mediawiki work in progress
This commit is contained in:
		
							parent
							
								
									bbd9b2c29e
								
							
						
					
					
						commit
						659e9f258a
					
				
					 2 changed files with 191 additions and 93 deletions
				
			
		|  | @ -7,11 +7,10 @@ use std::io::IsTerminal; | |||
| use std::os::linux::raw::stat; | ||||
| use std::time::Instant; | ||||
| 
 | ||||
| use chrono::{Local, NaiveDate}; | ||||
| use chrono::{Local, NaiveDate, Utc, DateTime}; | ||||
| use clap::{Arg, Command}; | ||||
| use colored::Colorize; | ||||
| use regex::Regex; | ||||
| 
 | ||||
| use cccron_lib::{trace_var, trace_var_, verboseln}; | ||||
| use cccron_lib::config_spec::{self, CfgField, CfgGroup, CfgSpec}; | ||||
| use cccron_lib::date; | ||||
|  | @ -485,7 +484,7 @@ fn do_protocol( | |||
|         ); | ||||
|         let _message_id = send_email(&subject, &body, email, config)?; | ||||
|         NYI!("convert to mediawiki"); | ||||
|         mediawiki::pad_ins_wiki(pad_content, wiki)?; | ||||
|         mediawiki::pad_ins_wiki(pad_content, wiki, plenum_day)?; | ||||
|         NYI!("add to wiki"); | ||||
|         config.set("state-name", &ProgramState::Logged.to_string()).ok(); | ||||
|     } else { | ||||
|  |  | |||
							
								
								
									
										279
									
								
								src/mediawiki.rs
									
										
									
									
									
								
							
							
						
						
									
										279
									
								
								src/mediawiki.rs
									
										
									
									
									
								
							|  | @ -1,7 +1,7 @@ | |||
| use std::cell::OnceCell; | ||||
| use std::error::Error; | ||||
| 
 | ||||
| use chrono::{Datelike, Utc}; | ||||
| use chrono::{Datelike, NaiveDate, Utc}; | ||||
| use colored::Colorize; | ||||
| use reqwest::blocking::Client; | ||||
| use serde::Deserialize; | ||||
|  | @ -183,89 +183,105 @@ impl Mediawiki { | |||
|     } | ||||
| 
 | ||||
|     /// 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, update_main_page: bool) -> Result<String, Box<dyn Error>> { | ||||
|         // 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.
 | ||||
|             ("text", page_content), // Add this text to the end of the page or section. Overrides text.
 | ||||
|             ("token", self.csrf_token.get().unwrap()), // A "csrf" token retrieved from action=query&meta=tokens
 | ||||
|             ("bot", "true"), // Mark this edit as a bot edit.
 | ||||
|         ]); | ||||
| 
 | ||||
|         println!("Making request to {} with params: {:?}", url, params); | ||||
|         
 | ||||
|         let request_result = self.make_request(url, params, ValidRequestTypes::Post); | ||||
| 
 | ||||
|         if update_main_page { | ||||
|             self.update_plenum_page(page_title)?; | ||||
|     pub fn new_wiki_page(&self, page_title: &str, page_content: &str, update_main_page: bool) -> Result<String, Box<dyn Error>> { | ||||
|         // Prevent dry run from making actual wiki edits
 | ||||
|         if self.is_dry_run { | ||||
|             println!("Dry run: Would create wiki page '{}' with content {}", page_title, page_content); | ||||
|             return Ok("Dry run - no actual page created".to_string()); | ||||
|         } | ||||
|     
 | ||||
|         // Ensure we have a CSRF token
 | ||||
|         if self.csrf_token.get().is_none() { | ||||
|             return Err("CSRF token not set. Call get_csrf_token() first.".into()); | ||||
|         } | ||||
|     
 | ||||
|         let url = format!("{}/api.php", self.server_url); | ||||
|         let params: Box<[(&str, &str)]> = Box::from([ | ||||
|             ("action", "edit"), | ||||
|             ("format", "json"), | ||||
|             ("title", page_title), | ||||
|             ("text", page_content), | ||||
|             ("token", self.csrf_token.get().unwrap()), | ||||
|             ("createonly", "true"), // Prevent overwriting existing pages
 | ||||
|             ("bot", "true"), | ||||
|         ]); | ||||
|     
 | ||||
|         // Log the request details for debugging
 | ||||
|         verboseln!("Creating wiki page: {} at {}", page_title, url); | ||||
|         
 | ||||
|         // Make the request to create the page
 | ||||
|         let request_result = self.make_request(url, params, ValidRequestTypes::PostForEditing)?; | ||||
|     
 | ||||
|         // Parse the response to check for success
 | ||||
|         let response: serde_json::Value = serde_json::from_str(&request_result)?; | ||||
|         
 | ||||
|         // Check if the page creation was successful
 | ||||
|         if let Some(edit) = response.get("edit") { | ||||
|             if edit.get("result").and_then(|r| r.as_str()) == Some("Success") { | ||||
|                 verboseln!("Successfully created wiki page: {}", page_title); | ||||
|                 
 | ||||
|                 // Update the main plenum page if requested
 | ||||
|                 if update_main_page { | ||||
|                     self.update_plenum_page(page_title)?; | ||||
|                 } | ||||
|                 
 | ||||
|                 Ok(request_result) | ||||
|             } else { | ||||
|                 Err(format!("Failed to create wiki page. Response: {}", response).into()) | ||||
|             } | ||||
|         } else { | ||||
|             Err(format!("Unexpected response when creating wiki page: {}", response).into()) | ||||
|         } | ||||
|         request_result | ||||
|     } | ||||
| 
 | ||||
|     /// This function is responsible for updating the main plenum page:
 | ||||
|     ///
 | ||||
|     /// It downloads the main plenum page from Mediawiki, inserts the
 | ||||
|     /// new Link to the newly uploaded plenum pad and uploads the
 | ||||
|     /// page back to mediawiki. 
 | ||||
|     pub fn update_plenum_page (&self, new_page_title_to_link_to: &str) -> Result<(), Box<dyn Error>> { | ||||
|         // 1. Get the current year:
 | ||||
|     pub fn update_plenum_page(&self, new_page_title_to_link_to: &str) -> Result<(), Box<dyn Error>> { | ||||
|         let current_year = Utc::now().year().to_string(); | ||||
|         let year_pattern = format!("=== {} ===", current_year); | ||||
| 
 | ||||
|         // 2. Download Plenum page content
 | ||||
|         let page_content = self.get_page_content(&self.plenum_main_page_name)?; | ||||
|         //trace_var!(page_content);
 | ||||
| 
 | ||||
|         // 3. Check if the current year exists
 | ||||
|         let mut updated_section = String::new(); | ||||
|         if page_content.contains(&year_pattern) { | ||||
|             // 4. Year Heading exists, inserting new link below
 | ||||
|             let mut content_split: Vec<&str> = page_content.split(&year_pattern).collect(); | ||||
|             let site_content_below_year_heading = content_split.pop().unwrap_or_default(); | ||||
|             let site_content_above_year_heading = content_split.pop().unwrap_or_default(); | ||||
|             updated_section = format!( | ||||
|                 "{}{}\n* [[{}]]{}", | ||||
|                 site_content_above_year_heading, | ||||
|                 year_pattern, | ||||
|                 new_page_title_to_link_to, | ||||
|                 site_content_below_year_heading); | ||||
|                 verboseln!("updated page: {}", updated_section) | ||||
|         } else { | ||||
|             // Scenario: New year: Generating new heading for the current year
 | ||||
|             // 4. First checking if the last year exists:
 | ||||
|             let last_year = current_year.parse::<i32>()? - 1; // returns e.g. 2023 when given 2024
 | ||||
|             let last_year_pattern = format!("=== {} ===", last_year); | ||||
|             if page_content.contains(&last_year_pattern) { | ||||
|                 // 2. Secondly, generate a new heading for this year above last year's
 | ||||
|                 verboseln!("new year, adding this year's heading"); | ||||
|                 let mut content_split: Vec<&str> = page_content.split(&last_year_pattern).collect(); | ||||
|                 let site_content_below_year_heading = content_split.pop().unwrap_or_default(); | ||||
|                 let site_content_above_year_heading = content_split.pop().unwrap_or_default(); | ||||
|                 updated_section = format!( | ||||
|                     "{}{}\n* [[{}]]\n\n{}{}", | ||||
|                     site_content_above_year_heading, | ||||
|                     year_pattern, | ||||
|                     new_page_title_to_link_to, | ||||
|                     last_year_pattern, | ||||
|                     site_content_below_year_heading); | ||||
|         let year_heading_pattern = format!("=== {} ===", current_year); | ||||
|     
 | ||||
|         // Download Plenum page content
 | ||||
|         let mut page_content = self.get_page_content(&self.plenum_main_page_name)?; | ||||
|     
 | ||||
|         // check if the current year heading pattern exists
 | ||||
|         if !page_content.contains(&year_heading_pattern) { | ||||
|             // If not, add a new year heading pattern
 | ||||
|             let last_year = (current_year.parse::<i32>()? - 1).to_string(); | ||||
|             let last_year_heading_pattern = format!("=== {} ===", last_year); | ||||
|     
 | ||||
|             if page_content.contains(&last_year_heading_pattern) { | ||||
|                 // add a new year heading pattern before the last year one
 | ||||
|                 let parts: Vec<&str> = page_content.split(&last_year_heading_pattern).collect(); | ||||
|                 page_content = format!( | ||||
|                     "{}=== {} ===\n* [[{}]]\n\n{}{}", | ||||
|                     parts[0], current_year, new_page_title_to_link_to, 
 | ||||
|                     last_year_heading_pattern, 
 | ||||
|                     parts[1] | ||||
|                 ); | ||||
|             } else { | ||||
|                 println!("There is neither a {}, nor a {} heading on the main plenum Page!", current_year, last_year); | ||||
|                 panic!("The Plenum Main Page seems seriously damaged, aborting...") | ||||
|                 // Fallback: add the current year heading to the end of the page
 | ||||
|                 page_content.push_str(&format!("\n\n=== {} ===\n* [[{}]]", current_year, new_page_title_to_link_to)); | ||||
|             } | ||||
|         } else { | ||||
|             // Paste the link below the current year heading
 | ||||
|             page_content = page_content.replace( | ||||
|                 &year_heading_pattern, | ||||
|                 &format!("{}\n* [[{}]]", year_heading_pattern, new_page_title_to_link_to) | ||||
|             ); | ||||
|         } | ||||
|         // uploading the pad again
 | ||||
|         self.new_wiki_page(&self.plenum_main_page_name, &updated_section, false)?; // nicht auf true setzen, sonst entsteht eine Endlosschleife
 | ||||
|     
 | ||||
|         // refresh page
 | ||||
|         self.new_wiki_page(&self.plenum_main_page_name, &page_content, false)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|     /// This function downloads and returns the contents of a wiki page when given the page's title (e.g. `page_title = Plenum/13._August_2024`)
 | ||||
|     pub fn get_page_content (&self, page_title: &str) -> Result<String, Box<dyn Error>> { | ||||
|         let url = | ||||
|             format!("{}/api.php?", self.server_url); | ||||
|     pub fn get_page_content(&self, page_title: &str) -> Result<String, Box<dyn Error>> { | ||||
|         let url = format!("{}/api.php?", self.server_url); | ||||
|         let params: Box<[(&str, &str)]> = Box::from([ | ||||
|             ("action", "parse"), // Create and edit pages.
 | ||||
|             ("action", "parse"), | ||||
|             ("prop", "wikitext"), | ||||
|             ("format", "json"), | ||||
|             ("page", page_title), | ||||
|  | @ -273,26 +289,118 @@ impl Mediawiki { | |||
|         ]); | ||||
|         let resp = self.make_request(url, params, ValidRequestTypes::Get)?; | ||||
|         let response_deserialized: serde_json::Value = serde_json::from_str(&resp)?; | ||||
| 
 | ||||
|         let resp = response_deserialized | ||||
|     
 | ||||
|         let wikitext = response_deserialized | ||||
|             .get("parse") | ||||
|             .and_then(|parse | parse.get("wikitext")) | ||||
|             .ok_or("Expected field `wikitext` not found, likely no access to main plenum page")?; | ||||
|         Ok(resp.to_string()) | ||||
|             .and_then(|parse| parse.get("wikitext")) | ||||
|             .and_then(|text| text.as_str()) | ||||
|             .ok_or("Expected field `wikitext` not found")?; | ||||
|         
 | ||||
|         Ok(wikitext.to_string()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn test_wiki_write(&self) -> Result<(), Box<dyn Error>> { | ||||
|         // Generate a unique test page title
 | ||||
|         let test_page_title = format!( | ||||
|             "TestPage/WikiWriteTest-{}", 
 | ||||
|             chrono::Utc::now().format("%Y%m%d%H%M%S") | ||||
|         ); | ||||
|         
 | ||||
|         // Test content to write
 | ||||
|         let test_content = format!( | ||||
|             "Wiki Write Test\n\nThis is a test page generated at {}. \nIt can be safely deleted.", | ||||
|             chrono::Utc::now() | ||||
|         ); | ||||
|     
 | ||||
|         // Ensure login token and CSRF token are set
 | ||||
|         self.get_login_token()?; | ||||
|         let _login_result = self.login()?; | ||||
|         self.get_csrf_token()?; | ||||
|     
 | ||||
|         let url = format!("{}/api.php", self.server_url); | ||||
|         let params: Box<[(&str, &str)]> = Box::from([ | ||||
|             ("action", "edit"), | ||||
|             ("format", "json"), | ||||
|             ("title", &test_page_title), | ||||
|             ("text", &test_content), | ||||
|             ("token", self.csrf_token.get().unwrap()), | ||||
|             ("createonly", "true"), | ||||
|             ("bot", "true"), | ||||
|         ]); | ||||
|     
 | ||||
|         // Manually print out all parameters for debugging
 | ||||
|         println!("Debug - URL: {}", url); | ||||
|         println!("Debug - Parameters:"); | ||||
|         for (key, value) in params.iter() { | ||||
|             println!("  {}: {}", key, value); | ||||
|         } | ||||
|     
 | ||||
|         // Make request and capture the full response
 | ||||
|         let request_result = match self.client | ||||
|             .post(&url) | ||||
|             .basic_auth(&self.http_user, Some(&self.http_password)) | ||||
|             .form(¶ms) | ||||
|             .send() 
 | ||||
|         { | ||||
|             Ok(response) => { | ||||
|                 println!("Debug - Response Status: {}", response.status()); | ||||
|                 println!("Debug - Response Headers: {:?}", response.headers()); | ||||
|                 
 | ||||
|                 match response.text() { | ||||
|                     Ok(text) => { | ||||
|                         println!("Debug - Raw Response Body:\n{}", text); | ||||
|                         text | ||||
|                     }, | ||||
|                     Err(e) => { | ||||
|                         return Err(format!("Failed to read response body: {}", e).into()); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             Err(e) => { | ||||
|                 return Err(format!("Request failed: {}", e).into()); | ||||
|             } | ||||
|         }; | ||||
|     
 | ||||
|         // Attempt to parse the response
 | ||||
|         match serde_json::from_str::<serde_json::Value>(&request_result) { | ||||
|             Ok(response) => { | ||||
|                 println!("Debug - Parsed Response: {}", response); | ||||
|                 
 | ||||
|                 if let Some(edit) = response.get("edit") { | ||||
|                     if edit.get("result").and_then(|r| r.as_str()) == Some("Success") { | ||||
|                         println!("✅ Successfully created test page: {}", test_page_title); | ||||
|                         Ok(()) | ||||
|                     } else { | ||||
|                         Err(format!("Failed to create page. Response: {}", response).into()) | ||||
|                     } | ||||
|                 } else { | ||||
|                     Err(format!("Unexpected response: {}", response).into()) | ||||
|                 } | ||||
|             } | ||||
|             Err(e) => { | ||||
|                 Err(format!("JSON parsing failed. Error: {}. Raw response: {}", e, request_result).into()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /// This function runs at the end of do_protocol() and is responsible for
 | ||||
| /// logging in to mediawiki, retrieving the necessary tokens, creating a
 | ||||
| /// new wiki page for the current plenum protocol, and for linking the new
 | ||||
| /// page to the main plenum overview page.
 | ||||
| pub fn pad_ins_wiki(old_pad_content: String, wiki: &Mediawiki) -> Result<(), Box<dyn Error>> { | ||||
| pub fn pad_ins_wiki(old_pad_content: String, wiki: &Mediawiki, plenum_date: &NaiveDate) -> Result<(), Box<dyn Error>> { | ||||
|     // Use the provided date or default to current date
 | ||||
|     let date = plenum_date; | ||||
|     wiki.test_wiki_write()?; | ||||
|     // Login to Wiki and get required tokens for logging in and writing
 | ||||
|     wiki.get_login_token()?; | ||||
|     verboseln!("Login token acquired."); | ||||
|     
 | ||||
|     let login_result = wiki.login()?; | ||||
|     verboseln!("Login done."); | ||||
|     trace_var!(login_result); | ||||
|     
 | ||||
|     wiki.get_csrf_token()?; | ||||
|     verboseln!("CSRF token acquired."); | ||||
| 
 | ||||
|  | @ -301,17 +409,12 @@ pub fn pad_ins_wiki(old_pad_content: String, wiki: &Mediawiki) -> Result<(), Box | |||
|     trace_var!(pad_converted); | ||||
|     
 | ||||
|     // Create a new wiki page plenum_main_page_name/page_title, e.g. under Plenum/13._August_2024
 | ||||
|     // and past the converted pad there
 | ||||
|     verboseln!("wiki: uploading converted pad"); | ||||
|     // let page_title = "14._August_2024";
 | ||||
|     let page_title = "14._AUGUST_2024"; | ||||
|     let page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title); | ||||
|     wiki.new_wiki_page(&page_title, &pad_converted, true)?; | ||||
|     let page_title = create_page_title(date); | ||||
|     let full_page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title); | ||||
|     
 | ||||
|     wiki.new_wiki_page(&full_page_title, &pad_converted, true)?; | ||||
| 
 | ||||
|     // Download the main plenum page and insert the new page_title as a link
 | ||||
|     verboseln!("wiki: updating main plenum page"); | ||||
| 
 | ||||
|     wiki.update_plenum_page(&page_title)?; | ||||
|     verboseln!("Finished successfully with wiki"); | ||||
|     Ok(()) | ||||
| } | ||||
|  | @ -332,14 +435,10 @@ fn pandoc_convert(markdown: String) -> Result<String, Box<dyn Error>> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| fn create_title (nächster_plenumstermin: String) { | ||||
|     let date_simple = NaiveDate::from(nächster_plenumstermin); | ||||
|     let wiki_page_title = format!("{} {} {}", date_simple.day(), LongMonthName[date_simple.month()], date_simple.year()); | ||||
| fn create_page_title(date: &NaiveDate) -> String { | ||||
|     date.format("%d. %B %Y").to_string() | ||||
| } | ||||
| 
 | ||||
|  */ | ||||
| 
 | ||||
| /// Deserialization must be done this way because the response contains
 | ||||
| /// two `\\` characters in both the login and csrf tokens, which breaks the
 | ||||
| /// usual deserialization
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 murmeldin
						murmeldin