split config & move email out into own file
In preparation for separate programs, the config spec is now built in main; each module defines its own group and main combines them all.
This commit is contained in:
		
							parent
							
								
									155bab8e5e
								
							
						
					
					
						commit
						0a992e2900
					
				
					 5 changed files with 234 additions and 142 deletions
				
			
		|  | @ -13,65 +13,9 @@ const ANSI_FIELD: &str = "\x1b[1;33m"; | |||
| const ANSI_NOTICE: &str = "\x1b[33m"; | ||||
| const ANSI_RESET: &str = "\x1b[0m"; | ||||
| 
 | ||||
| const CONFIG_SPEC: CfgSpec<'static> = CfgSpec { | ||||
|     groups: &[ | ||||
|         CfgGroup { name: "config", | ||||
|             description: "(internal values)", | ||||
|             fields: &[ | ||||
|                 CfgField::Silent { key: "version", default: "1" }, | ||||
|             ], | ||||
|         }, | ||||
|         CfgGroup { name: "hedgedoc", | ||||
|             description: "HedgeDoc markdown pad server settings", | ||||
|             fields: &[ | ||||
|                 CfgField::Default { key: "server-url", default: "https://md.berlin.ccc.de", description: "Hedgedoc server storing the pads." }, | ||||
|                 CfgField::Default { key: "template-name", default: "plenum-template", description: "Name of the pad containing the template to use." }, | ||||
|                 // TODO: make these generators?
 | ||||
|                 CfgField::Optional { key: "last-id", description: "ID of last plenum's pad." }, | ||||
|                 CfgField::Optional { key: "next-id", description: "ID of next plenum's pad." }, | ||||
|             ], | ||||
|         }, | ||||
|         CfgGroup { name: "email", | ||||
|             description: "Sending emails.", | ||||
|             fields: &[ | ||||
|                 CfgField::Default { key: "server", default: "mail.berlin.ccc.de", description: "SMTP server used for sending emails." }, | ||||
|                 CfgField::Default { key: "user", default: "plenum-bot@berlin.ccc.de", description: "User name used for authenticating with the mail server." }, | ||||
|                 CfgField::Password { key: "password", description: "Password for authenticating with the mail server." }, | ||||
|                 CfgField::Default { key: "sender", default: "Plenumsbot <plenum-bot@berlin.ccc.de>", description: "Email address to use for \"From:\"." }, | ||||
|                 CfgField::Default { key: "to", default: "CCCB Intern <intern@berlin.ccc.de>", description: "Recipient of the emails sent." }, | ||||
|                 CfgField::Optional { key: "last-message-id", description: "Message-Id of last initial announcement to send In-Reply-To (if applicable)." }, | ||||
|             ], | ||||
|         }, | ||||
|         CfgGroup { name: "wiki", | ||||
|             description: "API Settings for Mediawiki", | ||||
|             fields: &[ | ||||
|                 CfgField::Default { key: "server-url", default: "https://wiki.berlin.ccc.de/" , description: "Server running the wiki." }, | ||||
|                 CfgField::Default { key: "http-user", default: "cccb-wiki", description: "HTTP basic auth user name." }, | ||||
|                 CfgField::Password { key: "http-pass", description: "HTTP basic auth password." }, | ||||
|                 CfgField::Default { key: "api-user", default: "PlenumBot@PlenumBot-PW1", description: "API Username associated with the bot account used for edits." }, | ||||
|                 CfgField::Password { key: "api-secret", description: "API secret / \"password\" used for authenticating as the bot." }, | ||||
|             ], | ||||
|         }, | ||||
|         // TODO: Matrix,
 | ||||
|         CfgGroup { name: "text", | ||||
|             description: "Various strings used.", | ||||
|             fields: &[ | ||||
|                 CfgField::Default { key: "email-greeting", | ||||
|                     default: "Hallo liebe Mitreisende,", | ||||
|                     description: "\"Hello\"-greeting added at the start of every email." | ||||
|                 }, | ||||
|                 CfgField::Default { key: "email-signature", | ||||
|                     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." | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| /// Ensure all fields with known default values exist.
 | ||||
| pub fn populate_defaults(config: &KV) { | ||||
|     for group in CONFIG_SPEC.groups { | ||||
| pub fn populate_defaults(spec: &CfgSpec, config: &KV) { | ||||
|     for group in spec.groups { | ||||
|         for field in group.fields { | ||||
|             match field { | ||||
|                 CfgField::Silent { default, .. } => { | ||||
|  | @ -89,9 +33,9 @@ pub fn populate_defaults(config: &KV) { | |||
| /// Check all groups / fields and interactively edit as needed.
 | ||||
| ///
 | ||||
| /// Will report if the config is fine or has missing values.
 | ||||
| pub fn interactive_check(config: KV) -> Result<(), Box<dyn Error>> { | ||||
| pub fn interactive_check(spec: &CfgSpec, config: KV) -> Result<(), Box<dyn Error>> { | ||||
|     let mut all_valid: bool = true; | ||||
|     for group in CONFIG_SPEC.groups { | ||||
|     for group in spec.groups { | ||||
|         group_header(group.name, group.description); | ||||
|         let todo = needs_info(&config, group); | ||||
|         // TODO: add distinction between edit all / edit necessary only
 | ||||
|  | @ -306,7 +250,7 @@ fn prompt_password() -> String { | |||
| /* ***** config structure definition ***** */ | ||||
| 
 | ||||
| /// Describes a field in the configuration.
 | ||||
| enum CfgField<'a> { | ||||
| pub enum CfgField<'a> { | ||||
|     /// Silently writes a default to the DB if absent, never prompts for changes.
 | ||||
|     Silent { key: &'a str, default: &'a str }, | ||||
|     /// Writes a default, asks for changes.
 | ||||
|  | @ -326,17 +270,17 @@ enum CfgField<'a> { | |||
| 
 | ||||
| /// A group of related config fields. The final key of an inner value will be
 | ||||
| /// `{group.name}-{key}`.
 | ||||
| struct CfgGroup<'a> { | ||||
|     name: &'a str, | ||||
|     description: &'a str, | ||||
|     fields: &'a [CfgField<'a>], | ||||
| pub struct CfgGroup<'a> { | ||||
|     pub name: &'a str, | ||||
|     pub description: &'a str, | ||||
|     pub fields: &'a [CfgField<'a>], | ||||
| } | ||||
| 
 | ||||
| /// A description of the entire config, as a collection of named groups.
 | ||||
| ///
 | ||||
| /// Used both to populate defaults and to interactively adjust the configuration.
 | ||||
| struct CfgSpec<'a> { | ||||
|     groups: &'a [CfgGroup<'a>], | ||||
| pub struct CfgSpec<'a> { | ||||
|     pub groups: &'a [CfgGroup<'a>], | ||||
| } | ||||
| 
 | ||||
| impl<'a> CfgField<'a> { | ||||
							
								
								
									
										111
									
								
								src/email.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/email.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,111 @@ | |||
| use crate::config_spec::{CfgField, CfgGroup}; | ||||
| use lettre::message::{header, SinglePart}; | ||||
| use lettre::transport::smtp::authentication::Credentials; | ||||
| use lettre::{Message, SmtpTransport, Transport}; | ||||
| use std::error::Error; | ||||
| use uuid::Uuid; | ||||
| 
 | ||||
| pub const CONFIG: CfgGroup<'static> = CfgGroup { | ||||
|     name: "email", | ||||
|     description: "Sending emails.", | ||||
|     fields: &[ | ||||
|         CfgField::Default { | ||||
|             key: "server", | ||||
|             default: "mail.berlin.ccc.de", | ||||
|             description: "SMTP server used for sending emails.", | ||||
|         }, | ||||
|         CfgField::Default { | ||||
|             key: "user", | ||||
|             default: "plenum-bot@berlin.ccc.de", | ||||
|             description: "User name used for authenticating with the mail server.", | ||||
|         }, | ||||
|         CfgField::Password { | ||||
|             key: "password", | ||||
|             description: "Password for authenticating with the mail server.", | ||||
|         }, | ||||
|         CfgField::Default { | ||||
|             key: "sender", | ||||
|             default: "Plenumsbot <plenum-bot@berlin.ccc.de>", | ||||
|             description: "Email address to use for \"From:\".", | ||||
|         }, | ||||
|         CfgField::Default { | ||||
|             key: "to", | ||||
|             default: "CCCB Intern <intern@berlin.ccc.de>", | ||||
|             description: "Recipient of the emails sent.", | ||||
|         }, | ||||
|         CfgField::Optional { | ||||
|             key: "last-message-id", | ||||
|             description: | ||||
|                 "Message-Id of last initial announcement to send In-Reply-To (if applicable).", | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct Email { | ||||
|     server: String, | ||||
|     credentials: Credentials, | ||||
|     message_id_suffix: String, | ||||
|     is_dry_run: bool, | ||||
| } | ||||
| 
 | ||||
| pub struct SimpleEmail { | ||||
|     base: Email, | ||||
|     from: String, | ||||
|     to: String, | ||||
|     in_reply_to: Option<String>, | ||||
| } | ||||
| 
 | ||||
| impl SimpleEmail { | ||||
|     pub fn new(base: Email, from: &str, to: &str, in_reply_to: Option<String>) -> Self { | ||||
|         Self { base: base.clone(), from: from.to_string(), to: to.to_string(), in_reply_to } | ||||
|     } | ||||
|     pub fn send_email(&self, subject: String, body: String) -> Result<String, Box<dyn Error>> { | ||||
|         self.base.send_email( | ||||
|             self.from.clone(), | ||||
|             self.to.clone(), | ||||
|             subject, | ||||
|             body, | ||||
|             self.in_reply_to.clone(), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Email { | ||||
|     pub fn new(server: &str, user: &str, pass: &str, is_dry_run: bool) -> Self { | ||||
|         let message_id_suffix = user.split('@').next_back().unwrap_or(&server).to_string(); | ||||
|         let credentials = Credentials::new(user.to_string(), pass.to_string()); | ||||
|         Self { server: server.to_string(), credentials, message_id_suffix, is_dry_run } | ||||
|     } | ||||
| 
 | ||||
|     pub fn send_email( | ||||
|         &self, from: String, to: String, subject: String, body: String, in_reply_to: Option<String>, | ||||
|     ) -> Result<String, Box<dyn Error>> { | ||||
|         let message_id = Uuid::new_v4().to_string() + &self.message_id_suffix; | ||||
|         let email = Message::builder() | ||||
|             .from(from.parse().unwrap()) | ||||
|             .to(to.parse().unwrap()) | ||||
|             .message_id(Some(message_id.clone())); | ||||
|         let email = | ||||
|             if in_reply_to.is_some() { email.in_reply_to(in_reply_to.unwrap()) } else { email } | ||||
|                 .subject(subject) | ||||
|                 .singlepart( | ||||
|                     SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body), | ||||
|                 ) | ||||
|                 .unwrap(); | ||||
| 
 | ||||
|         if !self.is_dry_run { | ||||
|             let mailer = SmtpTransport::starttls_relay(&self.server)? | ||||
|                 .credentials(self.credentials.clone()) | ||||
|                 .build(); | ||||
|             mailer.send(&email)?; | ||||
|             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))") | ||||
|             ); | ||||
|             Ok("dummy-message-id@localhost".to_string()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,8 +1,28 @@ | |||
| use crate::config_spec::{CfgField, CfgGroup}; | ||||
| use reqwest::blocking::Client; | ||||
| use reqwest::blocking::Response; | ||||
| use std::error::Error; | ||||
| 
 | ||||
| // TODO: implement dry-run logic
 | ||||
| pub const CONFIG: CfgGroup<'static> = CfgGroup { | ||||
|     name: "hedgedoc", | ||||
|     description: "HedgeDoc markdown pad server settings", | ||||
|     fields: &[ | ||||
|         CfgField::Default { | ||||
|             key: "server-url", | ||||
|             default: "https://md.berlin.ccc.de", | ||||
|             description: "Hedgedoc server storing the pads.", | ||||
|         }, | ||||
|         CfgField::Default { | ||||
|             key: "template-name", | ||||
|             default: "plenum-template", | ||||
|             description: "Name of the pad containing the template to use.", | ||||
|         }, | ||||
|         // TODO: make these generators?
 | ||||
|         CfgField::Optional { key: "last-id", description: "ID of last plenum's pad." }, | ||||
|         CfgField::Optional { key: "next-id", description: "ID of next plenum's pad." }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| pub struct HedgeDoc { | ||||
|     server_url: String, | ||||
|     is_dry_run: bool, | ||||
|  | @ -35,6 +55,7 @@ impl HedgeDoc { | |||
|     } | ||||
| 
 | ||||
|     pub fn create_pad(&self) -> Result<String, Box<dyn Error>> { | ||||
|         if self.is_dry_run { todo!("NYI: sane dry-run behavior") } | ||||
|         let res = self.do_request(&format!("{}/new", self.server_url)).unwrap(); | ||||
|         if res.status().is_success() { | ||||
|             Ok(self.get_id_from_response(res)) | ||||
|  | @ -44,6 +65,7 @@ impl HedgeDoc { | |||
|     } | ||||
| 
 | ||||
|     pub fn import_note(&self, id: Option<&str>, content: String) -> Result<String, Box<dyn Error>> { | ||||
|         if self.is_dry_run { todo!("NYI: sane dry-run behavior") } | ||||
|         let url = match id { | ||||
|             Some(id) => self.format_url(&format!("new/{id}")), | ||||
|             None => self.format_url("new"), | ||||
|  |  | |||
							
								
								
									
										126
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										126
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -34,33 +34,58 @@ future improvements: | |||
|  */ | ||||
| // Import other .rs files as modules
 | ||||
| mod key_value; | ||||
| use email::{Email, SimpleEmail}; | ||||
| use key_value::KeyValueStore as KV; | ||||
| mod config_spec; | ||||
| use config_spec::{CfgField, CfgGroup, CfgSpec}; | ||||
| mod email; | ||||
| mod hedgedoc; | ||||
| use hedgedoc::HedgeDoc; | ||||
| mod mediawiki; | ||||
| mod config_check; | ||||
| 
 | ||||
| pub mod variables_and_settings; | ||||
| 
 | ||||
| use std::borrow::Cow; | ||||
| use std::env; | ||||
| use std::error::Error; | ||||
| use std::fs::File; | ||||
| use std::io::Read; | ||||
| // use std::future::Future;
 | ||||
| 
 | ||||
| use chrono::{Datelike, Local, NaiveDate, Weekday}; | ||||
| use clap::{Arg, Command}; | ||||
| use regex::Regex; | ||||
| use uuid::Uuid; | ||||
| 
 | ||||
| use lettre::message::{header, SinglePart}; | ||||
| use lettre::transport::smtp::authentication::Credentials; | ||||
| use lettre::{Message, SmtpTransport, Transport}; | ||||
| use reqwest::blocking::Client; | ||||
| 
 | ||||
| const FALLBACK_TEMPLATE: &str = variables_and_settings::FALLBACK_TEMPLATE; | ||||
| 
 | ||||
| /* ***** Config Spec ***** */ | ||||
| const CONFIG_SPEC: CfgSpec<'static> = CfgSpec { | ||||
|     groups: &[ | ||||
|         CfgGroup { name: "config", | ||||
|             description: "(internal values)", | ||||
|             fields: &[ | ||||
|                 CfgField::Silent { key: "version", default: "1" }, | ||||
|             ], | ||||
|         }, | ||||
|         hedgedoc::CONFIG, | ||||
|         mediawiki::CONFIG, | ||||
|         email::CONFIG, | ||||
|         // TODO: Matrix, …?
 | ||||
|         CfgGroup { name: "text", | ||||
|             description: "Various strings used.", | ||||
|             fields: &[ | ||||
|                 CfgField::Default { key: "email-greeting", | ||||
|                     default: "Hallo liebe Mitreisende,", | ||||
|                     description: "\"Hello\"-greeting added at the start of every email." | ||||
|                 }, | ||||
|                 CfgField::Default { key: "email-signature", | ||||
|                     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." | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| /* ***** Runtime Configuration (Arguments & Environment Variables) ***** */ | ||||
| 
 | ||||
| /// Checks environment variable `DRY_RUN` to see if any external operations
 | ||||
|  | @ -109,14 +134,26 @@ fn main() -> Result<(), Box<dyn Error>> { | |||
|     let args = parse_args(); | ||||
|     let config_file = args.config_file.as_str(); | ||||
|     let config = KV::new(config_file).unwrap(); | ||||
|     config_check::populate_defaults(&config); | ||||
|     config_spec::populate_defaults(&CONFIG_SPEC, &config); | ||||
|     if args.check_mode { | ||||
|         return config_check::interactive_check(config); | ||||
|         return config_spec::interactive_check(&CONFIG_SPEC, config); | ||||
|     } | ||||
|     // config
 | ||||
|     let hedgedoc = HedgeDoc::new(&config["hedgedoc-server-url"], is_dry_run()); | ||||
|     let email_ = Email::new( | ||||
|         &config["email-server"], | ||||
|         &config["email-user"], | ||||
|         &config["email-password"], | ||||
|         is_dry_run(), | ||||
|     ); | ||||
|     let email = SimpleEmail::new( | ||||
|         email_, | ||||
|         &config["email-from"], | ||||
|         &config["email-to"], | ||||
|         config.get("email-in-reply-to").ok(), | ||||
|     ); | ||||
|     println!("[START]\nAktueller Zustand der DB:"); | ||||
|     config.dump_redacting(&["email-password", "wiki-password", "matrix-password"]).ok(); | ||||
|     config.dump_redacting(&["email-password", "wiki-http-password", "wiki-api-secret", "matrix-password"]).ok(); | ||||
| 
 | ||||
|     // Dienstage diesen Monat
 | ||||
|     let all_tuesdays: Vec<NaiveDate> = get_all_weekdays(0, Weekday::Tue); | ||||
|  | @ -213,9 +250,8 @@ Bis morgen, 20 Uhr! | |||
| 
 | ||||
| {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!")) | ||||
|             message_id = email.send_email(betreff, message).ok() | ||||
|             // .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); | ||||
|  | @ -232,9 +268,8 @@ Bis zum nächsten Plenum. | |||
| {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!")) | ||||
|             message_id = email.send_email(betreff, message).ok() | ||||
|             // .expect("Plenum wird abgesagt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!"))
 | ||||
|         } | ||||
|     } else if in_3_days_is_plenum { | ||||
|         println!("In 3 Tagen ist Plenum, deshalb wird eine Erinnerung raus geschickt!"); | ||||
|  | @ -251,9 +286,8 @@ Hier ist der Link zum Pad, wo ihr noch Themen eintragen könnt: | |||
| 
 | ||||
| {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!")) | ||||
|             message_id = email.send_email(betreff, message).ok() | ||||
|             // .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
 | ||||
|  | @ -290,15 +324,15 @@ Und hier ist das Protokoll des letzten Plenums: | |||
|         ); | ||||
|         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!")); | ||||
|         message_id = email.send_email(betreff, message).ok(); | ||||
|         // .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]\nAktueller Zustand der DB:"); | ||||
|     config.dump_redacting(&["email-password", "matrix-password", "wiki-password"]).ok(); | ||||
|     config.dump_redacting(&["email-password", "wiki-http-password", "wiki-api-secret", "matrix-password"]).ok(); | ||||
| 
 | ||||
|     if config.has_errors() { | ||||
|         Err("There were errors.".into()) | ||||
|  | @ -350,52 +384,6 @@ fn create_tldr(pad_content: &String) -> Vec<&str> { | |||
|     //tldr_vec.append() = m.iter()
 | ||||
| } | ||||
| 
 | ||||
| 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"); | ||||
| 
 | ||||
|     let mail_to: &str = &config["email-to"]; | ||||
| 
 | ||||
|     let email = Message::builder() | ||||
|         // Set the sender's name and email address
 | ||||
|         .from(config["email-sender"].parse().unwrap()) | ||||
|         // Set the recipient's name and email address
 | ||||
|         .to(mail_to.parse().unwrap()) | ||||
|         .message_id(Option::from(message_id.clone())) | ||||
|         .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)) | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     if !is_dry_run() { | ||||
|         // Set up the SMTP client
 | ||||
|         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(); | ||||
| 
 | ||||
|         // Send the email
 | ||||
|         match mailer.send(&email) { | ||||
|             Ok(_) => println!("Email sent successfully!"), | ||||
|             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))") | ||||
|         ); | ||||
|         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, | ||||
|  |  | |||
|  | @ -1,12 +1,39 @@ | |||
| use std::error::Error; | ||||
| use std::fs::File; | ||||
| use std::io::Read; | ||||
| use clap::builder::Str; | ||||
| use crate::config_spec::{CfgField, CfgGroup}; | ||||
| use pandoc::{PandocError, PandocOutput}; | ||||
| use reqwest; | ||||
| use reqwest::blocking::Client; | ||||
| use reqwest::tls; | ||||
| use serde::Deserialize; | ||||
| use std::error::Error; | ||||
| use std::fs::File; | ||||
| use std::io::Read; | ||||
| 
 | ||||
| pub const CONFIG: CfgGroup<'static> = CfgGroup { | ||||
|     name: "wiki", | ||||
|     description: "API Settings for Mediawiki", | ||||
|     fields: &[ | ||||
|         CfgField::Default { | ||||
|             key: "server-url", | ||||
|             default: "https://wiki.berlin.ccc.de/", | ||||
|             description: "Server running the wiki.", | ||||
|         }, | ||||
|         CfgField::Default { | ||||
|             key: "http-user", | ||||
|             default: "cccb-wiki", | ||||
|             description: "HTTP basic auth user name.", | ||||
|         }, | ||||
|         CfgField::Password { key: "http-password", description: "HTTP basic auth password." }, | ||||
|         CfgField::Default { | ||||
|             key: "api-user", | ||||
|             default: "PlenumBot@PlenumBot-PW1", | ||||
|             description: "API Username associated with the bot account used for edits.", | ||||
|         }, | ||||
|         CfgField::Password { | ||||
|             key: "api-secret", | ||||
|             description: "API secret / \"password\" used for authenticating as the bot.", | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct QueryResponse { | ||||
|  | @ -76,4 +103,4 @@ pub fn get_login_token(client: &Client, http_user: &str, http_pass: &String) -> | |||
|         .text()?; | ||||
|     let response_deserialized: QueryResponse = serde_json::from_str(&resp)?; | ||||
|     Ok(response_deserialized.query.tokens.logintoken) | ||||
| } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 nobody
						nobody