diff --git a/Cargo.toml b/Cargo.toml index 13b3930..4d9bee8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ serde = {version = "1.0.204", features = ["derive"]} serde_json = "1.0.122" colored = "2.1.0" nom = "7.1.3" +mediawiki = "0.3.1" [[bin]] name = "Plenum-Bot" diff --git a/src/hedgedoc.rs b/src/hedgedoc.rs index 472f888..3e89651 100644 --- a/src/hedgedoc.rs +++ b/src/hedgedoc.rs @@ -54,9 +54,25 @@ impl HedgeDoc { } fn do_request(&self, url: &str) -> Result> { - Ok(self.client.get(url).send().unwrap()) + match self.client.get(url).send() { + Ok(response) => { + if response.status().is_success() { + Ok(response) + } else { + Err(format!("Failed to connect to hedgedoc server: HTTP status code {}", response.status()).into()) + } + } + Err(e) => { + if e.is_connect() { + Err("Failed to connect to hedgedoc server. Please check your internet connection or the server URL.".into()) + } else { + Err(format!("An error occurred while sending the request to the hedgedoc server: {}", e).into()) + } + } + } } + fn get_id_from_response(&self, res: Response) -> String { res.url().to_string().trim_start_matches(&format!("{}/", self.server_url)).to_string() } diff --git a/src/main.rs b/src/main.rs index 5722b02..088efb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,25 +22,25 @@ Pad-ins-Wiki-und-versenden-Skript main.rs - [ ] Add logic for top_anzahl in main.rs:172 MediaWiki -- [ ] Add "get_csrf_token-function" for getting a write token to allow write operations in the wiki (murmeldin) -- [ ] Add "create_page" function for creating new pages in the wiki that is called on every day after plenum (murmeldin) +- [X] Add "get_csrf_token-function" for getting a write token to allow write operations in the wiki (murmeldin) +- [X] Add "create_page" function for creating new pages in the wiki that is called on every day after plenum (murmeldin) - [ ] Add "modify_plenum_main_page" function for creating new Links on the Plenum main page whenever the create_page function is being called (murmeldin) +- [ ] Date Logic for create_page-function (maybe nobody or murmeldin when date_logic branch is done and merged) 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…) */ -use std::borrow::Cow; use std::env; use std::error::Error; +use std::time::Instant; use chrono::{Local, NaiveDate}; use clap::{Arg, Command}; use colored::Colorize; use regex::Regex; -use std::env; -use std::error::Error; + use std::fmt::Display; use std::io::IsTerminal; @@ -160,6 +160,7 @@ fn parse_args() -> Args { /* ***** Main ***** */ fn main() -> Result<(), Box> { + let start = Instant::now(); if std::io::stdout().is_terminal() { println!(include_str!("chaosknoten.txt"), VERSION = env!("CARGO_PKG_VERSION")); } @@ -275,6 +276,11 @@ fn main() -> Result<(), Box> { // shutdown config.set("state-last-run", &today.to_string())?; + + let duration = format!("{:?}", start.elapsed()); + config.set("state-eta", &duration).ok(); + verboseln!("Der Bot ist fertig und hat für die Ausführung {} gebraucht.", duration); + if config.has_errors() { Err("There were errors while writing config values.".into()) } else { @@ -679,3 +685,12 @@ fn send_email( ); email.send_email(full_subject, full_body) } + +fn display_logo(eta: &str) { + let ansi_art = r#" +▐▛▀ ▐▛▀ ▐▛▀ ▐▛▚ ▐▛▚ ▐▌ ▐▛▀ ▐▙ ▉ ▐▌ ▐▌ ▐▙ ▟▌ ▐▛▚ ▐▛▀▜▌▝▀█▀ +▐▌ ▐▌ ▐▌ ▐█▙ ▐▙▞ ▐▌ ▐▙▄ ▐▌▚ ▉ ▐▌ ▐▌ ▐▌▀▐▌ ▐█▙ ▐▌ ▐▌ █ +▐▙▄ ▐▙▄ ▐▙▄ ▐▙▞ ▐▌ ▐▙▄ ▐▙▄ ▐▌ ▚▉ ▐▙▄▟▌ ▐▌ ▐▌ ▐▙▞ ▐▙▄▟▌ █ Version 1.1,"#; + let ansi_art = format!("{ansi_art} ETA = {eta}\n"); + println!("{}", ansi_art.red()); +} \ No newline at end of file diff --git a/src/mediawiki.rs b/src/mediawiki.rs index 6d3b26b..31b062f 100644 --- a/src/mediawiki.rs +++ b/src/mediawiki.rs @@ -36,6 +36,11 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup { key: "api-secret", description: "API secret / \"password\" used for authenticating as the bot.", }, + CfgField::Default { + key: "eta", + default: "no ETA, program never ran", + description: "ETA message for estimating time the program takes" + } ], }; @@ -63,6 +68,12 @@ impl std::fmt::Debug for Mediawiki { } } +pub enum RequestType { + Get, + Post, + PostForEditing +} + impl Mediawiki { pub fn new( server_url: &str, http_auth_user: &str, http_auth_password: &str, api_user: &str, api_secret: &str, is_dry_run: bool, @@ -81,84 +92,155 @@ impl Mediawiki { } pub fn get_login_token(&self) -> Result> { let url = - format!("{}/api.php?action=query&meta=tokens&type=login&format=json", self.server_url); - let resp = self - .client - .get(url) - //.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT - .send()? - .text()?; - let response_deserialized: QueryResponse = serde_json::from_str(&resp)?; + format!("{}/api.php?", self.server_url); + let params: Box<[(&str, &str)]> = Box::from( [ + ("format", "json"), + ("meta", "tokens"), + ("type", "login"), + ("action", "query") + ]); + let resp = self.make_request(url, params, RequestType::Get).unwrap(); + let response_deserialized: QueryResponseLogin = serde_json::from_str(&resp)?; Ok(response_deserialized.query.tokens.logintoken) } pub fn login (&self) -> Result> { - let url = format!("{}/api.php?action=login", self.server_url); - let params = [ - ("lgname", &self.api_user), - ("lgpassword", &self.api_secret), - ("lgtoken", &self.login_token) - ]; - let resp = self - .client - .post(url) - .form(¶ms) - .send()? - .text()?; - Ok(resp) + let url = format!("{}/api.php?", self.server_url); + let params: Box<[(&str, &str)]> = Box::from([ + ("lgname", self.api_user.as_str()), + ("lgpassword", self.api_secret.as_str()), + ("lgtoken", &self.login_token), + ("action", "login") + ]); + let resp: Result> = self.make_request(url, params, RequestType::Post); + Ok(resp.unwrap()) } pub fn get_csrf_token(&self) -> Result> { // HAS TO BE FIXED - // action=query&meta=tokens let url = format!("{}/api.php?", self.server_url); - let params = [ + let params: Box<[(&str, &str)]> = Box::from([ ("format", "json"), ("meta", "tokens"), ("formatversion", "2"), - ("lgtoken", &self.login_token) - ]; - let resp = self - .client - .get(url) - //.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT - .query(¶ms) - .send()? - .text()?; - println!(" --- \n{}\n ---", resp.green()); - let response_deserialized: QueryResponse = serde_json::from_str(&resp)?; - Ok(response_deserialized.query.tokens.logintoken) + ("action", "query") + ]); + let resp: Result> = self.make_request(url, params, RequestType::Get); + let resp = resp.unwrap(); + let response_deserialized: QueryResponseCsrf = serde_json::from_str(&resp)?; + Ok(response_deserialized.query.tokens.csrftoken) } + pub fn make_request(&self, url: String, params: Box<[(&str, &str)]>, request_type: RequestType) -> Result> { + let resp: Result> = match + match request_type { + RequestType::Get => { + self + .client + .get(url) + //.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT + .query(¶ms) + .send() + } + RequestType::Post | RequestType::PostForEditing => { + self + .client + .post(url) + //.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT + .form(¶ms) + .send() + } + } + { + Ok(response) => { + if response.status().is_success() { + match request_type { + RequestType::PostForEditing => Ok(response.text().unwrap()), + _ => Ok(response.text().unwrap()) + } + } + else { + Err(format!("Failed to connect to wiki server: HTTP status code {}", response.status()).into()) + } + } + Err(e) => { + if e.is_connect() { + Err(format!("Failed to connect to wiki server. Please check your internet connection or the server URL.\n(Error: {})", e).into()) + } else { + Err(format!("An error occurred while sending the request to the wiki server: {}", e).into()) + } + } + }; + resp + } + /// Creates a completely new wiki page with page_content and page_title as inputs + pub fn new_wiki_page (&self, page_title: &str, page_content: &str) -> Result> { + // action=edit&format=json&title=Wikipedia:Sandbox&appendtext=Hello&token=sampleCsrfToken123+\ + let url = + format!("{}/api.php?", self.server_url); + let params: Box<[(&str, &str)]> = Box::from([ + ("action", "edit"), // Create and edit pages. + ("format", "json"), + ("title", page_title), // Title of the page to edit. Cannot be used together with pageid. + ("appendtext", page_content), // Add this text to the end of the page or section. Overrides text. + ("token", self.csrf_token.as_str()), // A "csrf" token retrieved from action=query&meta=tokens + ("bot", "true"), // Mark this edit as a bot edit. + ]); + self.make_request(url, params, RequestType::Post) + } } #[derive(Deserialize)] -struct QueryResponse { +struct QueryResponseLogin { batchcomplete: String, - query: QueryTokens, + query: QueryTokensLogin, } #[derive(Deserialize)] -struct QueryTokens { - tokens: Tokens, +struct QueryTokensLogin { + tokens: TokensLogin, } #[derive(Deserialize)] -struct Tokens { +struct TokensLogin { logintoken: String, } -pub fn pad_ins_wiki(old_pad_content: String, wiki: &mut Mediawiki) -> Result<(), Box> { - convert_md_to_mediawiki(old_pad_content); +#[derive(Deserialize)] +struct QueryResponseCsrf { + batchcomplete: bool, + query: crate::mediawiki::QueryTokensCsrf, +} - let auth_result = wiki.get_login_token().unwrap(); - wiki.login_token = auth_result.clone(); +#[derive(Deserialize)] +struct QueryTokensCsrf { + tokens: crate::mediawiki::TokensCsrf, +} + +#[derive(Deserialize)] +struct TokensCsrf { + csrftoken: String, +} + +pub fn pad_ins_wiki(old_pad_content: String, wiki: &mut Mediawiki) -> Result<(), Box> { + // Login to Wiki and get required tokens for logging in and writing + let auth_result = wiki.get_login_token()?; + wiki.login_token.clone_from(&auth_result); println!("AUTH Success"); let login_result = wiki.login()?; println!("LOGIN Success"); let csrf_token = wiki.get_csrf_token(); + let csrf_token = csrf_token.unwrap_or_else(|e| { + println!("Error while trying to get csrf: {:?}", e); + String::new() + }); println!("CSRF Success"); - //wiki.csrf_token.clone_from(&csrf_token); HAS TO BE FIXED - println!("---AUTH RESULT:---\n{}\n---LOGIN RESULT:---\n{:?}\n---CSRF RESULT:---\n\n-----------", auth_result, login_result,/* csrf_token*/); - + wiki.csrf_token.clone_from(&csrf_token); + println!("---AUTH RESULT:---\n{}\n---LOGIN RESULT:---\n{:?}\n---CSRF RESULT:---\n{}\n-----------", auth_result, login_result, csrf_token); + + // Convert to mediawiki and make new page + let pad_converted = convert_md_to_mediawiki(old_pad_content); + println!("Das kommt ins Wiki: {}", pad_converted); + //wiki.new_wiki_page("Page Test 5", &pad_converted)?; JUST AN EXAMPLE + // Textdatei wieder einlesen // Passwörter aus Datenbank lesen (ToBeDone) @@ -192,11 +274,11 @@ fn pandoc_convert( /// Reads a text file from a specified path and returns it as a String fn read_txt_file(filepath: &str) -> String { let mut file = File::open(filepath) - .expect(&*format!("Fehler beim öffnen der Textdatei mit Pfad {filepath}!")); + .unwrap_or_else(|_| panic!("Fehler beim öffnen der Textdatei mit Pfad {filepath}!")); let mut contents = String::new(); file.read_to_string(&mut contents) - .expect("Fehler beim auslesen der MediaWiki-Textdatei!") - .to_string() + .expect("Fehler beim auslesen der MediaWiki-Textdatei!"); + contents } /// Takes a Sting in the Markdown format and returns a String in the mediawiki Format @@ -204,12 +286,8 @@ fn convert_md_to_mediawiki(old_pad_content: String) -> String { // TODO: use tempfile="3.3", make it a NamedTempFile::new()?; // or alternatively use piped stdout to avoid files entirely let output_filepath: &str = "./pandoc_mediawiki.txt"; - pandoc_convert( - old_pad_content, - output_filepath, - pandoc::InputFormat::Markdown, - pandoc::OutputFormat::MediaWiki, - ) - .expect("Fehler beim Umwandeln des und speichern des Pads in eine mediawiki-Textdatei"); - read_txt_file(output_filepath) + pandoc_convert(old_pad_content, output_filepath, pandoc::InputFormat::Markdown, pandoc::OutputFormat::MediaWiki).expect("Fehler beim Umwandeln des und speichern des Pads in eine mediawiki-Textdatei"); + let temp = read_txt_file(output_filepath); + println!("TEMP: {}", temp.purple()); + temp }