mit cargo fmt alles formatiert
This commit is contained in:
		
							parent
							
								
									c026a38239
								
							
						
					
					
						commit
						c8031dfc01
					
				
					 5 changed files with 256 additions and 154 deletions
				
			
		| 
						 | 
				
			
			@ -3,14 +3,14 @@ use std::error::Error;
 | 
			
		|||
use std::io::{self, Write};
 | 
			
		||||
 | 
			
		||||
/// Text shown for an empty field.
 | 
			
		||||
const EMPTY_VALUE : &str = "(empty)";
 | 
			
		||||
const EMPTY_VALUE: &str = "(empty)";
 | 
			
		||||
/// Text shown in place of a password.
 | 
			
		||||
const HIDDEN_PASSWORD : &str = "*****";
 | 
			
		||||
const HIDDEN_PASSWORD: &str = "*****";
 | 
			
		||||
 | 
			
		||||
// highlight some of the info
 | 
			
		||||
const ANSI_GROUP: &str = "\x1b[1;31m";
 | 
			
		||||
const ANSI_FIELD: &str = "\x1b[1;33m";
 | 
			
		||||
const ANSI_NOTICE: &str= "\x1b[33m";
 | 
			
		||||
const ANSI_NOTICE: &str = "\x1b[33m";
 | 
			
		||||
const ANSI_RESET: &str = "\x1b[0m";
 | 
			
		||||
 | 
			
		||||
const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ pub fn populate_defaults(config: &KV) {
 | 
			
		|||
                CfgField::Default { default, .. } => {
 | 
			
		||||
                    config.default(&field.full_key(group.name), default)
 | 
			
		||||
                },
 | 
			
		||||
                _ => {}
 | 
			
		||||
                _ => {},
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -80,33 +80,47 @@ pub fn populate_defaults(config: &KV) {
 | 
			
		|||
///
 | 
			
		||||
/// Will report if the config is fine or has missing values.
 | 
			
		||||
pub fn interactive_check(config: KV) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    let mut all_valid : bool = true;
 | 
			
		||||
    let mut all_valid: bool = true;
 | 
			
		||||
    for group in CONFIG_SPEC.groups {
 | 
			
		||||
        group_header( group.name, group.description );
 | 
			
		||||
        group_header(group.name, group.description);
 | 
			
		||||
        let todo = needs_info(&config, group);
 | 
			
		||||
        // TODO: add distinction between edit all / edit necessary only
 | 
			
		||||
        let choices = if !todo.is_empty() {
 | 
			
		||||
            println!( "{}The following fields need adjustment: {}{}", ANSI_NOTICE, todo.join(", "), ANSI_RESET );
 | 
			
		||||
            println!(
 | 
			
		||||
                "{}The following fields need adjustment: {}{}",
 | 
			
		||||
                ANSI_NOTICE,
 | 
			
		||||
                todo.join(", "),
 | 
			
		||||
                ANSI_RESET
 | 
			
		||||
            );
 | 
			
		||||
            &["[E]dit (default)", "[L]ist all", "[S]kip group"]
 | 
			
		||||
        } else {
 | 
			
		||||
            println!( "{}This group looks fine. OK to [S]kip, unless you want to adjust values here.{}", ANSI_NOTICE, ANSI_RESET );
 | 
			
		||||
            &["[S]kip group (default)", "[E]dit", "[L]ist all", ]
 | 
			
		||||
            println!(
 | 
			
		||||
                "{}This group looks fine. OK to [S]kip, unless you want to adjust values here.{}",
 | 
			
		||||
                ANSI_NOTICE, ANSI_RESET
 | 
			
		||||
            );
 | 
			
		||||
            &["[S]kip group (default)", "[E]dit", "[L]ist all"]
 | 
			
		||||
        };
 | 
			
		||||
        loop {
 | 
			
		||||
            let choice = prompt_action( choices );
 | 
			
		||||
            let choice = prompt_action(choices);
 | 
			
		||||
            match choice {
 | 
			
		||||
                'L' => { show_group(&config, group); },
 | 
			
		||||
                'L' => {
 | 
			
		||||
                    show_group(&config, group);
 | 
			
		||||
                },
 | 
			
		||||
                'E' => {
 | 
			
		||||
                    for field in group.fields {
 | 
			
		||||
                        check_field( &config, group.name, field, &mut all_valid )?;
 | 
			
		||||
                        check_field(&config, group.name, field, &mut all_valid)?;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                },
 | 
			
		||||
                'S' => {
 | 
			
		||||
                    if !todo.is_empty() { all_valid = false; }
 | 
			
		||||
                    if !todo.is_empty() {
 | 
			
		||||
                        all_valid = false;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                },
 | 
			
		||||
                _ => { unreachable!(); }, // (prompt already checks)
 | 
			
		||||
                _ => {
 | 
			
		||||
                    unreachable!();
 | 
			
		||||
                }, // (prompt already checks)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -117,30 +131,49 @@ pub fn interactive_check(config: KV) -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn check_field( config: &KV, grpname: &str, field: &CfgField, ok: &mut bool ) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    if field.is_silent() { return Ok(()) }
 | 
			
		||||
fn check_field(
 | 
			
		||||
    config: &KV, grpname: &str, field: &CfgField, ok: &mut bool,
 | 
			
		||||
) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    if field.is_silent() {
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
    show_field(config, grpname, field);
 | 
			
		||||
    let key = field.full_key(grpname);
 | 
			
		||||
    let value = config.get(&key).ok();
 | 
			
		||||
    let mut actions: Vec<&'static str> = Vec::new();
 | 
			
		||||
    // TODO: adjust order: empty password should offer input first
 | 
			
		||||
    // TODO: RandomId should offer generating as [R]egenerate
 | 
			
		||||
    if value.is_some() | field.is_password() | field.is_optional() { actions.push("[K]eep as-is"); }
 | 
			
		||||
    if field.default_description().is_some() { actions.push("[R]eset to default"); }
 | 
			
		||||
    if field.is_password() { actions.push( "[R]emove" ); }
 | 
			
		||||
    actions.push( "[I]nput new value" );
 | 
			
		||||
    if !field.is_password() { actions.push( "[L]ong (multiline) input" ) };
 | 
			
		||||
    if value.is_some() | field.is_password() | field.is_optional() {
 | 
			
		||||
        actions.push("[K]eep as-is");
 | 
			
		||||
    }
 | 
			
		||||
    if field.default_description().is_some() {
 | 
			
		||||
        actions.push("[R]eset to default");
 | 
			
		||||
    }
 | 
			
		||||
    if field.is_password() {
 | 
			
		||||
        actions.push("[R]emove");
 | 
			
		||||
    }
 | 
			
		||||
    actions.push("[I]nput new value");
 | 
			
		||||
    if !field.is_password() {
 | 
			
		||||
        actions.push("[L]ong (multiline) input")
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match prompt_action(&actions) {
 | 
			
		||||
        'K' => {
 | 
			
		||||
            // we allow leaving a password empty, but that means config is incomplete
 | 
			
		||||
            if value.is_none() & !field.is_optional() { *ok &= false; }
 | 
			
		||||
            if value.is_none() & !field.is_optional() {
 | 
			
		||||
                *ok &= false;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'R' => {
 | 
			
		||||
            match field.default_value()? {
 | 
			
		||||
                Some(value) => { config.set(&key, &value)?; },
 | 
			
		||||
                Some(value) => {
 | 
			
		||||
                    config.set(&key, &value)?;
 | 
			
		||||
                },
 | 
			
		||||
                // password again
 | 
			
		||||
                None => { config.delete(&key)?; *ok &= false; },
 | 
			
		||||
                None => {
 | 
			
		||||
                    config.delete(&key)?;
 | 
			
		||||
                    *ok &= false;
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        'I' => {
 | 
			
		||||
| 
						 | 
				
			
			@ -151,43 +184,49 @@ fn check_field( config: &KV, grpname: &str, field: &CfgField, ok: &mut bool ) ->
 | 
			
		|||
            let value = prompt_multiline();
 | 
			
		||||
            config.set(&key, &value)?;
 | 
			
		||||
        },
 | 
			
		||||
        _ => { return Err("Wat.".into()); }
 | 
			
		||||
        _ => {
 | 
			
		||||
            return Err("Wat.".into());
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ***** displaying various kinds of info ***** */
 | 
			
		||||
 | 
			
		||||
fn group_header( name: &str, description: &str ) {
 | 
			
		||||
fn group_header(name: &str, description: &str) {
 | 
			
		||||
    println!("==============================");
 | 
			
		||||
    println!("{}{}{} - {}", ANSI_GROUP, name, ANSI_RESET, description);
 | 
			
		||||
    println!("");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn show_group( config: &KV, group: &CfgGroup ) {
 | 
			
		||||
fn show_group(config: &KV, group: &CfgGroup) {
 | 
			
		||||
    for field in group.fields {
 | 
			
		||||
        show_field( &config, group.name, field );
 | 
			
		||||
        show_field(&config, group.name, field);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn show_field( config: &KV, grpname: &str, field: &CfgField ) {
 | 
			
		||||
    if field.is_silent() { return }
 | 
			
		||||
fn show_field(config: &KV, grpname: &str, field: &CfgField) {
 | 
			
		||||
    if field.is_silent() {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    let key = field.full_key(grpname);
 | 
			
		||||
    println!( "{}{}{} - {}", ANSI_FIELD, &key, ANSI_RESET, field.description() );
 | 
			
		||||
    println!( "  default: {}", field.default_description().unwrap_or(EMPTY_VALUE.to_string()));
 | 
			
		||||
    let value = config.get(&key).ok()
 | 
			
		||||
    println!("{}{}{} - {}", ANSI_FIELD, &key, ANSI_RESET, field.description());
 | 
			
		||||
    println!("  default: {}", field.default_description().unwrap_or(EMPTY_VALUE.to_string()));
 | 
			
		||||
    let value = config
 | 
			
		||||
        .get(&key)
 | 
			
		||||
        .ok()
 | 
			
		||||
        .map(|s| if field.is_password() { HIDDEN_PASSWORD.to_string() } else { s })
 | 
			
		||||
        .unwrap_or(EMPTY_VALUE.to_string());
 | 
			
		||||
    println!( "  current: {}", value );
 | 
			
		||||
    println!("  current: {}", value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ***** basic validation ***** */
 | 
			
		||||
 | 
			
		||||
fn needs_info( config: &KV, group: &CfgGroup ) -> Vec<String> {
 | 
			
		||||
fn needs_info(config: &KV, group: &CfgGroup) -> Vec<String> {
 | 
			
		||||
    let mut acc = Vec::new();
 | 
			
		||||
    for field in group.fields {
 | 
			
		||||
        if field.is_action_required(config, group.name) {
 | 
			
		||||
            acc.push( field.key() );
 | 
			
		||||
            acc.push(field.key());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    acc
 | 
			
		||||
| 
						 | 
				
			
			@ -197,12 +236,12 @@ fn needs_info( config: &KV, group: &CfgGroup ) -> Vec<String> {
 | 
			
		|||
 | 
			
		||||
/// Ask for a choice between several options.  First option is the default,
 | 
			
		||||
/// format options with the first character in brackets like so: `"[E]xample"`.
 | 
			
		||||
fn prompt_action( choices: &[&str] ) -> char {
 | 
			
		||||
fn prompt_action(choices: &[&str]) -> char {
 | 
			
		||||
    let prompt_message = choices.join(", ");
 | 
			
		||||
    let default_choice = choices[0].to_uppercase().chars().nth(1).unwrap_or_default();
 | 
			
		||||
    loop {
 | 
			
		||||
        println!( "{}", prompt_message );
 | 
			
		||||
        print!( "Select: " );
 | 
			
		||||
        println!("{}", prompt_message);
 | 
			
		||||
        print!("Select: ");
 | 
			
		||||
        io::stdout().flush().ok();
 | 
			
		||||
 | 
			
		||||
        // Read line and take first non-space character
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +250,10 @@ fn prompt_action( choices: &[&str] ) -> char {
 | 
			
		|||
        let input = input.trim().to_uppercase().chars().next().unwrap_or(default_choice);
 | 
			
		||||
 | 
			
		||||
        // Check list of choices for match
 | 
			
		||||
        if choices.iter().any(|&choice| choice.to_uppercase().chars().nth(1).unwrap_or_default() == input) {
 | 
			
		||||
        if choices
 | 
			
		||||
            .iter()
 | 
			
		||||
            .any(|&choice| choice.to_uppercase().chars().nth(1).unwrap_or_default() == input)
 | 
			
		||||
        {
 | 
			
		||||
            return input;
 | 
			
		||||
        } else {
 | 
			
		||||
            println!("Invalid choice. Try again!");
 | 
			
		||||
| 
						 | 
				
			
			@ -220,7 +262,7 @@ fn prompt_action( choices: &[&str] ) -> char {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Read a single line of text.
 | 
			
		||||
fn prompt_single_line( ) -> String {
 | 
			
		||||
fn prompt_single_line() -> String {
 | 
			
		||||
    print!("New value: ");
 | 
			
		||||
    io::stdout().flush().ok();
 | 
			
		||||
    let mut input = String::new();
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +271,7 @@ fn prompt_single_line( ) -> String {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Read multiple lines of text, terminated by a line containing just a '.'.
 | 
			
		||||
fn prompt_multiline( ) -> String {
 | 
			
		||||
fn prompt_multiline() -> String {
 | 
			
		||||
    println!("Enter new value: (end with '.' on a new line)");
 | 
			
		||||
    let mut acc = String::new();
 | 
			
		||||
    loop {
 | 
			
		||||
| 
						 | 
				
			
			@ -244,7 +286,7 @@ fn prompt_multiline( ) -> String {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/// Read a password without echoing it.
 | 
			
		||||
fn prompt_password( ) -> String {
 | 
			
		||||
fn prompt_password() -> String {
 | 
			
		||||
    let pass = rpassword::prompt_password("New password (not shown) : ").unwrap();
 | 
			
		||||
    // disabled echo means the newline also isn't shown
 | 
			
		||||
    println!("");
 | 
			
		||||
| 
						 | 
				
			
			@ -264,7 +306,12 @@ enum CfgField<'a> {
 | 
			
		|||
    /// Empty by default, required, will prompt without echo.
 | 
			
		||||
    Password { key: &'a str, description: &'a str },
 | 
			
		||||
    /// Empty by default, can be user-provided, or will be generated randomly.
 | 
			
		||||
    RandomId { key: &'a str, generator: fn() -> Result<String,Box<dyn Error>>, generator_description: &'a str, description: &'a str },
 | 
			
		||||
    RandomId {
 | 
			
		||||
        key: &'a str,
 | 
			
		||||
        generator: fn() -> Result<String, Box<dyn Error>>,
 | 
			
		||||
        generator_description: &'a str,
 | 
			
		||||
        description: &'a str,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A group of related config fields. The final key of an inner value will be
 | 
			
		||||
| 
						 | 
				
			
			@ -285,17 +332,17 @@ struct CfgSpec<'a> {
 | 
			
		|||
impl<'a> CfgField<'a> {
 | 
			
		||||
    /// Silent fields don't get prompted or shown.
 | 
			
		||||
    fn is_silent(&self) -> bool {
 | 
			
		||||
        matches!( self, CfgField::Silent { .. } )
 | 
			
		||||
        matches!(self, CfgField::Silent { .. })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Password fields will be censored when displayed.
 | 
			
		||||
    fn is_password(&self) -> bool {
 | 
			
		||||
        matches!( self, CfgField::Password { .. } )
 | 
			
		||||
        matches!(self, CfgField::Password { .. })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Optional fields are allowed to be (and stay) empty.
 | 
			
		||||
    fn is_optional(&self) -> bool {
 | 
			
		||||
        matches!( self, CfgField::Optional { .. } )
 | 
			
		||||
        matches!(self, CfgField::Optional { .. })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Reports if the field needs changing or is ok as-is.
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +351,10 @@ impl<'a> CfgField<'a> {
 | 
			
		|||
    /// *necessarily* always have a value. Optional is optional.  Only
 | 
			
		||||
    /// Password and RandomId might need actions if they are missing.)
 | 
			
		||||
    fn is_action_required(&self, config: &KV, grpname: &str) -> bool {
 | 
			
		||||
        if matches!( self, CfgField::Silent { .. } | CfgField::Default { .. } | CfgField::Optional { .. } ) {
 | 
			
		||||
        if matches!(
 | 
			
		||||
            self,
 | 
			
		||||
            CfgField::Silent { .. } | CfgField::Default { .. } | CfgField::Optional { .. }
 | 
			
		||||
        ) {
 | 
			
		||||
            false
 | 
			
		||||
        } else {
 | 
			
		||||
            config.get(self.full_key(grpname).as_str()).ok().is_none()
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +369,8 @@ impl<'a> CfgField<'a> {
 | 
			
		|||
            CfgField::Optional { key, .. } => key,
 | 
			
		||||
            CfgField::Password { key, .. } => key,
 | 
			
		||||
            CfgField::RandomId { key, .. } => key,
 | 
			
		||||
        }.to_string()
 | 
			
		||||
        }
 | 
			
		||||
        .to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Full field name / key used in the DB (currently `{grpname}-{fieldname}`.)
 | 
			
		||||
| 
						 | 
				
			
			@ -335,7 +386,8 @@ impl<'a> CfgField<'a> {
 | 
			
		|||
            CfgField::Optional { description, .. } => description,
 | 
			
		||||
            CfgField::Password { description, .. } => description,
 | 
			
		||||
            CfgField::RandomId { description, .. } => description,
 | 
			
		||||
        }.to_string()
 | 
			
		||||
        }
 | 
			
		||||
        .to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets a description of the default value if one exists.  
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +396,9 @@ impl<'a> CfgField<'a> {
 | 
			
		|||
        match self {
 | 
			
		||||
            CfgField::Silent { default, .. } => Some(default.to_string()),
 | 
			
		||||
            CfgField::Default { default, .. } => Some(default.to_string()),
 | 
			
		||||
            CfgField::RandomId { generator_description, .. } => Some(format!("{}{}{}",'(',generator_description,')')),
 | 
			
		||||
            CfgField::RandomId { generator_description, .. } => {
 | 
			
		||||
                Some(format!("{}{}{}", '(', generator_description, ')'))
 | 
			
		||||
            },
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,63 +1,61 @@
 | 
			
		|||
use reqwest::blocking::Response;
 | 
			
		||||
use reqwest::blocking::Client;
 | 
			
		||||
use reqwest::blocking::Response;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
// TODO: implement dry-run logic
 | 
			
		||||
pub struct HedgeDoc {
 | 
			
		||||
    server_url : String,
 | 
			
		||||
    is_dry_run : bool,
 | 
			
		||||
    client : Client,
 | 
			
		||||
    server_url: String,
 | 
			
		||||
    is_dry_run: bool,
 | 
			
		||||
    client: Client,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HedgeDoc {
 | 
			
		||||
    pub fn new( server_url: &str, is_dry_run: bool ) -> Self {
 | 
			
		||||
    pub fn new(server_url: &str, is_dry_run: bool) -> Self {
 | 
			
		||||
        Self { server_url: server_url.to_string(), is_dry_run, client: Client::new() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn format_url( &self, pad_name: &str ) -> String {
 | 
			
		||||
        format!( "{}/{}", self.server_url, pad_name )
 | 
			
		||||
    pub fn format_url(&self, pad_name: &str) -> String {
 | 
			
		||||
        format!("{}/{}", self.server_url, pad_name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn format_action( &self, pad_name: &str, verb: &str ) -> String {
 | 
			
		||||
        format!( "{}/{}/{}", self.server_url, pad_name, verb )
 | 
			
		||||
    fn format_action(&self, pad_name: &str, verb: &str) -> String {
 | 
			
		||||
        format!("{}/{}/{}", self.server_url, pad_name, verb)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn do_request(&self, url : &str ) -> Result<Response, Box<dyn Error>> {
 | 
			
		||||
        Ok(self.client.get( url ).send().unwrap())
 | 
			
		||||
    fn do_request(&self, url: &str) -> Result<Response, Box<dyn Error>> {
 | 
			
		||||
        Ok(self.client.get(url).send().unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_id_from_response( &self, res : Response ) -> String {
 | 
			
		||||
        res.url().to_string().trim_start_matches( &format!( "{}/", self.server_url ) ).to_string()
 | 
			
		||||
    fn get_id_from_response(&self, res: Response) -> String {
 | 
			
		||||
        res.url().to_string().trim_start_matches(&format!("{}/", self.server_url)).to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn download( &self, pad_name: &str ) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        Ok(self.do_request( &self.format_action(pad_name, "download"))?.text()?)
 | 
			
		||||
    pub fn download(&self, pad_name: &str) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        Ok(self.do_request(&self.format_action(pad_name, "download"))?.text()?)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn create_pad( &self ) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let res = self.do_request( &format!( "{}/new", self.server_url ) ).unwrap();
 | 
			
		||||
    pub fn create_pad(&self) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let res = self.do_request(&format!("{}/new", self.server_url)).unwrap();
 | 
			
		||||
        if res.status().is_success() {
 | 
			
		||||
            Ok(self.get_id_from_response(res))
 | 
			
		||||
        } else {
 | 
			
		||||
            Err( format!("Failed to create pad {}", res.status()).into() )
 | 
			
		||||
            Err(format!("Failed to create pad {}", res.status()).into())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn import_note( &self, id: Option<&str>, content: String ) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    pub fn import_note(&self, id: Option<&str>, content: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let url = match id {
 | 
			
		||||
            Some(id) => self.format_url( &format!( "new/{id}" ) ),
 | 
			
		||||
            Some(id) => self.format_url(&format!("new/{id}")),
 | 
			
		||||
            None => self.format_url("new"),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let res = self.client.post(&url)
 | 
			
		||||
            .header( "Content-Type", "text/markdown" )
 | 
			
		||||
            .body(content)
 | 
			
		||||
            .send()?;
 | 
			
		||||
        let res =
 | 
			
		||||
            self.client.post(&url).header("Content-Type", "text/markdown").body(content).send()?;
 | 
			
		||||
 | 
			
		||||
        if res.status().is_success() {
 | 
			
		||||
            Ok(self.get_id_from_response(res))
 | 
			
		||||
        } else {
 | 
			
		||||
            Err( format!("Failed to import note: {}", res.status()).into() )
 | 
			
		||||
            Err(format!("Failed to import note: {}", res.status()).into())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										185
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										185
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -32,12 +32,11 @@ mod mediawiki;
 | 
			
		|||
 | 
			
		||||
mod config_check;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub mod variables_and_settings;
 | 
			
		||||
 | 
			
		||||
use std::borrow::Cow;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::Read;
 | 
			
		||||
// use std::future::Future;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,9 +46,9 @@ use clap::{Arg, Command};
 | 
			
		|||
use regex::Regex;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
use lettre::{Message, SmtpTransport, Transport};
 | 
			
		||||
use lettre::message::{header, SinglePart};
 | 
			
		||||
use lettre::transport::smtp::authentication::Credentials;
 | 
			
		||||
use lettre::{Message, SmtpTransport, Transport};
 | 
			
		||||
 | 
			
		||||
const FALLBACK_TEMPLATE: &str = variables_and_settings::FALLBACK_TEMPLATE;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +85,8 @@ fn parse_args() -> Args {
 | 
			
		|||
                .action(clap::ArgAction::Set)
 | 
			
		||||
                .default_value("config.sqlite")
 | 
			
		||||
                .help("specifies an alternate config file"),
 | 
			
		||||
        ).get_matches();
 | 
			
		||||
        )
 | 
			
		||||
        .get_matches();
 | 
			
		||||
    Args {
 | 
			
		||||
        check_mode: *matches.get_one::<bool>("check").unwrap(),
 | 
			
		||||
        config_file: matches.get_one::<String>("config_file").unwrap().clone(),
 | 
			
		||||
| 
						 | 
				
			
			@ -102,12 +102,12 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    let config = KV::new(config_file).unwrap();
 | 
			
		||||
    config_check::populate_defaults(&config);
 | 
			
		||||
    if args.check_mode {
 | 
			
		||||
        return config_check::interactive_check( config );
 | 
			
		||||
        return config_check::interactive_check(config);
 | 
			
		||||
    }
 | 
			
		||||
    // config
 | 
			
		||||
    let hedgedoc = HedgeDoc::new( &config["hedgedoc-server-url"], is_dry_run() );
 | 
			
		||||
    let hedgedoc = HedgeDoc::new(&config["hedgedoc-server-url"], is_dry_run());
 | 
			
		||||
    println!("[START]\nAktueller Zustand der DB:");
 | 
			
		||||
    config.dump_redacting(&["email-password","wiki-password","matrix-password"]).ok();
 | 
			
		||||
    config.dump_redacting(&["email-password", "wiki-password", "matrix-password"]).ok();
 | 
			
		||||
 | 
			
		||||
    // Dienstage diesen Monat
 | 
			
		||||
    let all_tuesdays: Vec<NaiveDate> = get_all_weekdays(0, Weekday::Tue);
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +120,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    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 = Local::now();
 | 
			
		||||
    let today_simple = today.date_naive();
 | 
			
		||||
    let yesterday = today_simple.pred_opt().unwrap();
 | 
			
		||||
    let in_1_day = today_simple.succ_opt().unwrap();
 | 
			
		||||
| 
						 | 
				
			
			@ -129,23 +129,25 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
 | 
			
		||||
    // 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,
 | 
			
		||||
    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 {
 | 
			
		||||
    } else {
 | 
			
		||||
        &vierter_dienstag
 | 
			
		||||
    };
 | 
			
		||||
    let übernächster_plenumtermin = if all_tuesdays[1] >= yesterday { // hier das Gleiche.
 | 
			
		||||
    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.
 | 
			
		||||
    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);
 | 
			
		||||
| 
						 | 
				
			
			@ -153,8 +155,12 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    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 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: Option<String> = None;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +178,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    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();
 | 
			
		||||
        let mut tldr = String::new();
 | 
			
		||||
        for element in tldr_vec {
 | 
			
		||||
            tldr.push_str("\n");
 | 
			
		||||
            tldr.push_str(&element)
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +186,8 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
        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#"{email_greeting}
 | 
			
		||||
            let message: String = format!(
 | 
			
		||||
                r#"{email_greeting}
 | 
			
		||||
 | 
			
		||||
Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
 | 
			
		||||
  {current_pad_link}
 | 
			
		||||
| 
						 | 
				
			
			@ -190,14 +197,16 @@ Und hier ein TL;DR von den aktuellen Themen:
 | 
			
		|||
 | 
			
		||||
Bis morgen, 20 Uhr!
 | 
			
		||||
 | 
			
		||||
{email_signature}"#); // ADJ_TIMEYWIMEY
 | 
			
		||||
{email_signature}"#
 | 
			
		||||
            ); // ADJ_TIMEYWIMEY
 | 
			
		||||
            println!("---E-Mail:---\n{}\n-----------", message);
 | 
			
		||||
            // XXX option x expect
 | 
			
		||||
            message_id = Some(mail_versenden(&config, betreff, message).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#"{email_greeting}
 | 
			
		||||
            let message: String = format!(
 | 
			
		||||
                r#"{email_greeting}
 | 
			
		||||
 | 
			
		||||
Es gibt keine Themen, deshalb wird das morgige Plenum leider nicht statt finden.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +215,9 @@ Hier ist der Link zum Pad vom nächsten Plenum, das am {} statt finden wird:
 | 
			
		|||
 | 
			
		||||
Bis zum nächsten Plenum.
 | 
			
		||||
 | 
			
		||||
{email_signature}"#, nächster_plenumtermin);
 | 
			
		||||
{email_signature}"#,
 | 
			
		||||
                nächster_plenumtermin
 | 
			
		||||
            );
 | 
			
		||||
            println!("---E-Mail:---\n{}\n-----------", message);
 | 
			
		||||
            // XXX option x expect
 | 
			
		||||
            message_id = Some(mail_versenden(&config, betreff, message).expect("Plenum wird abgesagt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!"))
 | 
			
		||||
| 
						 | 
				
			
			@ -216,29 +227,38 @@ Bis zum nächsten Plenum.
 | 
			
		|||
        if number_of_tops(&pad_content_without_top_instructions) == 0 {
 | 
			
		||||
            // Mail an alle senden und sagen, dass es noch keine Themen gibt
 | 
			
		||||
            let betreff = format!("Plenum vom {}: Bisher noch keine Themen", nächster_plenumtermin);
 | 
			
		||||
            let message: String = format!(r#"{email_greeting}
 | 
			
		||||
            let message: String = format!(
 | 
			
		||||
                r#"{email_greeting}
 | 
			
		||||
 | 
			
		||||
Es sind bisher leider keine Themen zusammengekommen.  Wenn es bis Sonntag Abend keine Themen gibt, wird das Plenum voraussichtlich nicht statt finden.
 | 
			
		||||
 | 
			
		||||
Hier ist der Link zum Pad, wo ihr noch Themen eintragen könnt:
 | 
			
		||||
  {current_pad_link}
 | 
			
		||||
 | 
			
		||||
{email_signature}"#);
 | 
			
		||||
{email_signature}"#
 | 
			
		||||
            );
 | 
			
		||||
            println!("---E-Mail:---\n{}\n-----------", message);
 | 
			
		||||
            // XXX option x expect
 | 
			
		||||
            message_id = Some(mail_versenden(&config, betreff, message).expect("Noch nicht genug Themen. 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(¤t_pad_id).expect("Fehler beim Hedgedoc-Pad-Download!");
 | 
			
		||||
        let old_pad_content =
 | 
			
		||||
            hedgedoc.download(¤t_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"));
 | 
			
		||||
        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 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");
 | 
			
		||||
        let tldr = tldr_vec.join("\n");
 | 
			
		||||
        // XXX nächstes/übernächstes?
 | 
			
		||||
        let message: String = format!(r#"{email_greeting}
 | 
			
		||||
        let message: String = format!(
 | 
			
		||||
            r#"{email_greeting}
 | 
			
		||||
 | 
			
		||||
Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
 | 
			
		||||
  {current_pad_link}
 | 
			
		||||
| 
						 | 
				
			
			@ -252,8 +272,10 @@ Und hier ist das Protokoll des letzten Plenums:
 | 
			
		|||
 | 
			
		||||
{old_pad_content_without_top_instructions}
 | 
			
		||||
 | 
			
		||||
{email_signature}"#);
 | 
			
		||||
        let betreff: String = format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl);
 | 
			
		||||
{email_signature}"#
 | 
			
		||||
        );
 | 
			
		||||
        let betreff: String =
 | 
			
		||||
            format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl);
 | 
			
		||||
        println!("---E-Mail:---\n{}\n-----------", message);
 | 
			
		||||
        // XXX option x expect
 | 
			
		||||
        message_id = Some(mail_versenden(&config, betreff, message).expect("Mail mit Plenumsprotokoll wurde versucht zu senden, konnte aber nicht gesendet werden!"));
 | 
			
		||||
| 
						 | 
				
			
			@ -262,19 +284,24 @@ Und hier ist das Protokoll des letzten Plenums:
 | 
			
		|||
    println!("message id: {:?}", message_id);
 | 
			
		||||
 | 
			
		||||
    println!("[ENDE]\nAktueller Zustand der DB:");
 | 
			
		||||
    config.dump_redacting(&["email-password","matrix-password","wiki-password"]).ok();
 | 
			
		||||
    
 | 
			
		||||
    if config.has_errors() { Err("There were errors.".into()) } else { Ok(()) }
 | 
			
		||||
    config.dump_redacting(&["email-password", "matrix-password", "wiki-password"]).ok();
 | 
			
		||||
 | 
			
		||||
    if config.has_errors() {
 | 
			
		||||
        Err("There were errors.".into())
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_all_weekdays(month_offset: i32, week_day: Weekday) -> Vec<NaiveDate>{
 | 
			
		||||
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)),
 | 
			
		||||
        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");
 | 
			
		||||
        _ => 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();
 | 
			
		||||
| 
						 | 
				
			
			@ -290,30 +317,28 @@ fn get_all_weekdays(month_offset: i32, week_day: Weekday) -> Vec<NaiveDate>{
 | 
			
		|||
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
 | 
			
		||||
    }
 | 
			
		||||
    return if infrage_kommendes_plenum == date_to_check { true } else { false };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn number_of_tops (pad_content: &String) -> i32 {
 | 
			
		||||
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> {
 | 
			
		||||
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();
 | 
			
		||||
    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 mail_versenden(config: &KV, betreff: String, inhalt: String) -> std::result::Result<String, Box<dyn std::error::Error>> {
 | 
			
		||||
fn mail_versenden(
 | 
			
		||||
    config: &KV, betreff: String, inhalt: String,
 | 
			
		||||
) -> std::result::Result<String, Box<dyn std::error::Error>> {
 | 
			
		||||
    // Define the email
 | 
			
		||||
    let message_id: String = Uuid::new_v4().to_string() + &String::from("@berlin.ccc.de");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -328,59 +353,74 @@ fn mail_versenden(config: &KV, betreff: String, inhalt: String) -> std::result::
 | 
			
		|||
        .in_reply_to("19de3985-05b4-42f3-9a6a-e941479be2ed@berlin.ccc.de".to_string())
 | 
			
		||||
        // Set the subject of the email
 | 
			
		||||
        .subject(betreff)
 | 
			
		||||
        .singlepart(SinglePart::builder()
 | 
			
		||||
            .header(header::ContentType::TEXT_PLAIN)
 | 
			
		||||
            .body(inhalt))
 | 
			
		||||
        .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(inhalt))
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    if !is_dry_run() {
 | 
			
		||||
        // Set up the SMTP client
 | 
			
		||||
        let creds = Credentials::new(config["email-user"].to_string(), config["email-password"].to_string());
 | 
			
		||||
        let creds = Credentials::new(
 | 
			
		||||
            config["email-user"].to_string(),
 | 
			
		||||
            config["email-password"].to_string(),
 | 
			
		||||
        );
 | 
			
		||||
        // Open a remote connection to gmail
 | 
			
		||||
 | 
			
		||||
        let mailer = SmtpTransport::starttls_relay(&config["email-server"])?
 | 
			
		||||
            .credentials(creds)
 | 
			
		||||
            .build();
 | 
			
		||||
        let mailer =
 | 
			
		||||
            SmtpTransport::starttls_relay(&config["email-server"])?.credentials(creds).build();
 | 
			
		||||
 | 
			
		||||
        // Send the email
 | 
			
		||||
        match mailer.send(&email) {
 | 
			
		||||
            Ok(_) => println!("Email sent successfully!"),
 | 
			
		||||
            Err(e) => eprintln!("Could not send email: {:?}", e),    }
 | 
			
		||||
            Err(e) => eprintln!("Could not send email: {:?}", e),
 | 
			
		||||
        }
 | 
			
		||||
        Ok(message_id)
 | 
			
		||||
    } else {
 | 
			
		||||
        println!( "[DRY RUN - NOT] sending email\n(raw message:)\n{}", std::str::from_utf8(&email.formatted()).unwrap_or("((UTF-8 error))") );
 | 
			
		||||
        println!(
 | 
			
		||||
            "[DRY RUN - NOT] sending email\n(raw message:)\n{}",
 | 
			
		||||
            std::str::from_utf8(&email.formatted()).unwrap_or("((UTF-8 error))")
 | 
			
		||||
        );
 | 
			
		||||
        Ok("dummy-message-id@localhost".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn generate_new_pad_for_following_date(config : KV, hedgedoc : HedgeDoc, übernächster_plenumtermin: &String, überübernächster_plenumtermin: &String, kv: &KV) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
 | 
			
		||||
    match hedgedoc.create_pad( ) {
 | 
			
		||||
fn generate_new_pad_for_following_date(
 | 
			
		||||
    config: KV, hedgedoc: HedgeDoc, übernächster_plenumtermin: &String,
 | 
			
		||||
    überübernächster_plenumtermin: &String, kv: &KV,
 | 
			
		||||
) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    match hedgedoc.create_pad() {
 | 
			
		||||
        Err(e) => println!("Failed to create pad: {}", e),
 | 
			
		||||
        Ok(pad_id) => {
 | 
			
		||||
            println!("Pad created successfully with ID: {}", pad_id);
 | 
			
		||||
 | 
			
		||||
            // Get the most recent plenum template and replace the placeholders:
 | 
			
		||||
            let template_content: String = match config.get("hedgedoc-template-name") {
 | 
			
		||||
                Ok(content) => hedgedoc.download(&content.clone()).unwrap_or_else(|_| FALLBACK_TEMPLATE.to_string()),
 | 
			
		||||
                Ok(content) => hedgedoc
 | 
			
		||||
                    .download(&content.clone())
 | 
			
		||||
                    .unwrap_or_else(|_| FALLBACK_TEMPLATE.to_string()),
 | 
			
		||||
                Err(_) => FALLBACK_TEMPLATE.to_string(),
 | 
			
		||||
            };
 | 
			
		||||
            // XXX you don't just use the template as-is…
 | 
			
		||||
            let template_modified: String = replace_placeholders(&template_content, übernächster_plenumtermin, überübernächster_plenumtermin).unwrap_or_else(|error |template_content); // Try regex, if not successful use without regex
 | 
			
		||||
            let template_modified: String = replace_placeholders(
 | 
			
		||||
                &template_content,
 | 
			
		||||
                übernächster_plenumtermin,
 | 
			
		||||
                überübernächster_plenumtermin,
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap_or_else(|error| template_content); // Try regex, if not successful use without regex
 | 
			
		||||
 | 
			
		||||
            match hedgedoc.import_note( Some(&pad_id), template_modified ) {
 | 
			
		||||
            match hedgedoc.import_note(Some(&pad_id), template_modified) {
 | 
			
		||||
                Ok(_) => {
 | 
			
		||||
                    println!("Pad updated successfully with template content.");
 | 
			
		||||
                    rotate (&pad_id, kv);
 | 
			
		||||
                    rotate(&pad_id, kv);
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => println!("Failed to update pad: {}", e),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn replace_placeholders(template: &str, übernächster_plenumtermin: &str, überübernächster_plenumtermin: &str) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
fn replace_placeholders(
 | 
			
		||||
    template: &str, übernächster_plenumtermin: &str, überübernächster_plenumtermin: &str,
 | 
			
		||||
) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    let re_datum = Regex::new(r"\{\{Datum\}\}")?;
 | 
			
		||||
    let result = re_datum.replace_all(template, übernächster_plenumtermin);
 | 
			
		||||
    let re_naechstes_plenum = Regex::new(r"\{\{naechstes-plenum\}\}")?;
 | 
			
		||||
| 
						 | 
				
			
			@ -388,18 +428,23 @@ fn replace_placeholders(template: &str, übernächster_plenumtermin: &str, über
 | 
			
		|||
    Ok(result.to_string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn rotate (future_pad_id: &str, kv: &KV) {
 | 
			
		||||
fn rotate(future_pad_id: &str, kv: &KV) {
 | 
			
		||||
    let next_plenum_pad = kv.get("zukünftiges-plenumspad").ok();
 | 
			
		||||
    if next_plenum_pad == None {
 | 
			
		||||
        kv.set("zukünftiges-plenumspad", &future_pad_id).expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!"); // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
        kv.set("zukünftiges-plenumspad", &future_pad_id)
 | 
			
		||||
            .expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!");
 | 
			
		||||
    // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
    } else {
 | 
			
		||||
        kv.set("aktuelles-plenumspad", &next_plenum_pad.unwrap()).expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!"); // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
        kv.set("zukünftiges-plenumspad", &future_pad_id).expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!"); // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
        kv.set("aktuelles-plenumspad", &next_plenum_pad.unwrap())
 | 
			
		||||
            .expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!"); // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
        kv.set("zukünftiges-plenumspad", &future_pad_id)
 | 
			
		||||
            .expect("Fehler beim Beschreiben der Datenbank mit neuem Plenumslink!");
 | 
			
		||||
        // Beispiel: aktuelles-plenumspad: Ok(Some("eCH24zXGS9S8Stg5xI3aRg"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn try_to_remove_top_instructions (pad_content: String) -> String {
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,18 +6,20 @@ pub fn pad_ins_wiki(old_pad_content: String) {
 | 
			
		|||
    convert_markdown_to_mediawiki_and_save_as_txt(old_pad_content);
 | 
			
		||||
 | 
			
		||||
    // Textdatei wieder einlesen
 | 
			
		||||
    let mut file = File::open("pandoc-output.txt").expect("Fehler beim öffnen der MediaWiki-Textdatei!");
 | 
			
		||||
    let mut file =
 | 
			
		||||
        File::open("pandoc-output.txt").expect("Fehler beim öffnen der MediaWiki-Textdatei!");
 | 
			
		||||
    let mut contents = String::new();
 | 
			
		||||
    file.read_to_string(&mut contents).expect("Fehler beim auslesen der MediaWiki-Textdatei!");
 | 
			
		||||
 | 
			
		||||
    // Passwörter aus Datenbank lesen (ToBeDone)
 | 
			
		||||
    let plenum_bot_user = String::from("PlenumBot@PlenumBot-PW1");
 | 
			
		||||
    let plenum_bot_pw = String::from("**OLD_API_PW_REMOVED**");
 | 
			
		||||
    let login_token = login_to_mediawiki(plenum_bot_user.clone(), plenum_bot_pw.clone()).expect("Fehler beim Einloggen!");
 | 
			
		||||
    let login_token = login_to_mediawiki(plenum_bot_user.clone(), plenum_bot_pw.clone())
 | 
			
		||||
        .expect("Fehler beim Einloggen!");
 | 
			
		||||
    println!("plenum_bot_user: {plenum_bot_user}, plenum_bot_pw: {plenum_bot_pw}, login_token: {login_token}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn convert_markdown_to_mediawiki_and_save_as_txt (old_pad_content: String) {
 | 
			
		||||
fn convert_markdown_to_mediawiki_and_save_as_txt(old_pad_content: String) {
 | 
			
		||||
    //Convert Markdown into Mediawiki
 | 
			
		||||
    // Vanilla pandoc Befehl: pandoc --from markdown --to mediawiki --no-highlight
 | 
			
		||||
    let mut p = pandoc::new();
 | 
			
		||||
| 
						 | 
				
			
			@ -25,10 +27,13 @@ fn convert_markdown_to_mediawiki_and_save_as_txt (old_pad_content: String) {
 | 
			
		|||
    p.set_input_format(pandoc::InputFormat::Markdown, vec![]);
 | 
			
		||||
    p.set_output(pandoc::OutputKind::File("./pandoc-output.txt".parse().unwrap()));
 | 
			
		||||
    p.set_output_format(pandoc::OutputFormat::MediaWiki, vec![]);
 | 
			
		||||
    p.execute().expect("Fehler beim Umwandeln des und speichern des Pads in eine mediawiki-Textdatei");
 | 
			
		||||
    p.execute()
 | 
			
		||||
        .expect("Fehler beim Umwandeln des und speichern des Pads in eine mediawiki-Textdatei");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn login_to_mediawiki (plenum_bot_user: String, plenum_bot_pw: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
fn login_to_mediawiki(
 | 
			
		||||
    plenum_bot_user: String, plenum_bot_pw: String,
 | 
			
		||||
) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    //let mut map = HashMap::new();
 | 
			
		||||
    //map.insert("logintoken", "result");
 | 
			
		||||
    let username = "cccb-wiki";
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +55,7 @@ fn login_to_mediawiki (plenum_bot_user: String, plenum_bot_pw: String) -> Result
 | 
			
		|||
    let html_source = resp.text()?;
 | 
			
		||||
    //let login_token: String = map.get("logintoken").unwrap().to_string().clone();
 | 
			
		||||
    println!("---HTML:---\n{}\n-----------", html_source);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
     */
 | 
			
		||||
    Ok(String::from("unimplemented"))
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,4 +40,4 @@ Nächstes Plenum: Am 2024-08-13 um 20 Uhr
 | 
			
		|||
---"#;
 | 
			
		||||
 | 
			
		||||
pub const PLENUM_TEMPLATE_URL: &str = "https://md.berlin.ccc.de/plenum-template/download";
 | 
			
		||||
pub const HEDGEDOC_SERVER_URL: &str = "https://md.berlin.ccc.de";
 | 
			
		||||
pub const HEDGEDOC_SERVER_URL: &str = "https://md.berlin.ccc.de";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue