mediawiki work in progress
This commit is contained in:
parent
bbd9b2c29e
commit
659e9f258a
|
@ -7,11 +7,10 @@ use std::io::IsTerminal;
|
||||||
use std::os::linux::raw::stat;
|
use std::os::linux::raw::stat;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use chrono::{Local, NaiveDate};
|
use chrono::{Local, NaiveDate, Utc, DateTime};
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, Command};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use cccron_lib::{trace_var, trace_var_, verboseln};
|
use cccron_lib::{trace_var, trace_var_, verboseln};
|
||||||
use cccron_lib::config_spec::{self, CfgField, CfgGroup, CfgSpec};
|
use cccron_lib::config_spec::{self, CfgField, CfgGroup, CfgSpec};
|
||||||
use cccron_lib::date;
|
use cccron_lib::date;
|
||||||
|
@ -485,7 +484,7 @@ fn do_protocol(
|
||||||
);
|
);
|
||||||
let _message_id = send_email(&subject, &body, email, config)?;
|
let _message_id = send_email(&subject, &body, email, config)?;
|
||||||
NYI!("convert to mediawiki");
|
NYI!("convert to mediawiki");
|
||||||
mediawiki::pad_ins_wiki(pad_content, wiki)?;
|
mediawiki::pad_ins_wiki(pad_content, wiki, plenum_day)?;
|
||||||
NYI!("add to wiki");
|
NYI!("add to wiki");
|
||||||
config.set("state-name", &ProgramState::Logged.to_string()).ok();
|
config.set("state-name", &ProgramState::Logged.to_string()).ok();
|
||||||
} else {
|
} else {
|
||||||
|
|
279
src/mediawiki.rs
279
src/mediawiki.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use chrono::{Datelike, Utc};
|
use chrono::{Datelike, NaiveDate, Utc};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -183,89 +183,105 @@ impl Mediawiki {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a completely new wiki page with page_content and page_title as inputs
|
/// 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>> {
|
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+\
|
// Prevent dry run from making actual wiki edits
|
||||||
let url =
|
if self.is_dry_run {
|
||||||
format!("{}/api.php?", self.server_url);
|
println!("Dry run: Would create wiki page '{}' with content {}", page_title, page_content);
|
||||||
let params: Box<[(&str, &str)]> = Box::from([
|
return Ok("Dry run - no actual page created".to_string());
|
||||||
("action", "edit"), // Create and edit pages.
|
}
|
||||||
("format", "json"),
|
|
||||||
("title", page_title), // Title of the page to edit. Cannot be used together with pageid.
|
// Ensure we have a CSRF token
|
||||||
("text", page_content), // Add this text to the end of the page or section. Overrides text.
|
if self.csrf_token.get().is_none() {
|
||||||
("token", self.csrf_token.get().unwrap()), // A "csrf" token retrieved from action=query&meta=tokens
|
return Err("CSRF token not set. Call get_csrf_token() first.".into());
|
||||||
("bot", "true"), // Mark this edit as a bot edit.
|
}
|
||||||
]);
|
|
||||||
|
let url = format!("{}/api.php", self.server_url);
|
||||||
println!("Making request to {} with params: {:?}", url, params);
|
let params: Box<[(&str, &str)]> = Box::from([
|
||||||
|
("action", "edit"),
|
||||||
let request_result = self.make_request(url, params, ValidRequestTypes::Post);
|
("format", "json"),
|
||||||
|
("title", page_title),
|
||||||
if update_main_page {
|
("text", page_content),
|
||||||
self.update_plenum_page(page_title)?;
|
("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:
|
/// This function is responsible for updating the main plenum page:
|
||||||
///
|
///
|
||||||
/// It downloads the main plenum page from Mediawiki, inserts the
|
/// It downloads the main plenum page from Mediawiki, inserts the
|
||||||
/// new Link to the newly uploaded plenum pad and uploads the
|
/// new Link to the newly uploaded plenum pad and uploads the
|
||||||
/// page back to mediawiki.
|
/// page back to mediawiki.
|
||||||
pub fn update_plenum_page (&self, new_page_title_to_link_to: &str) -> Result<(), Box<dyn Error>> {
|
pub fn update_plenum_page(&self, new_page_title_to_link_to: &str) -> Result<(), Box<dyn Error>> {
|
||||||
// 1. Get the current year:
|
|
||||||
let current_year = Utc::now().year().to_string();
|
let current_year = Utc::now().year().to_string();
|
||||||
let year_pattern = format!("=== {} ===", current_year);
|
let year_heading_pattern = format!("=== {} ===", current_year);
|
||||||
|
|
||||||
// 2. Download Plenum page content
|
// Download Plenum page content
|
||||||
let page_content = self.get_page_content(&self.plenum_main_page_name)?;
|
let mut page_content = self.get_page_content(&self.plenum_main_page_name)?;
|
||||||
//trace_var!(page_content);
|
|
||||||
|
// check if the current year heading pattern exists
|
||||||
// 3. Check if the current year exists
|
if !page_content.contains(&year_heading_pattern) {
|
||||||
let mut updated_section = String::new();
|
// If not, add a new year heading pattern
|
||||||
if page_content.contains(&year_pattern) {
|
let last_year = (current_year.parse::<i32>()? - 1).to_string();
|
||||||
// 4. Year Heading exists, inserting new link below
|
let last_year_heading_pattern = format!("=== {} ===", last_year);
|
||||||
let mut content_split: Vec<&str> = page_content.split(&year_pattern).collect();
|
|
||||||
let site_content_below_year_heading = content_split.pop().unwrap_or_default();
|
if page_content.contains(&last_year_heading_pattern) {
|
||||||
let site_content_above_year_heading = content_split.pop().unwrap_or_default();
|
// add a new year heading pattern before the last year one
|
||||||
updated_section = format!(
|
let parts: Vec<&str> = page_content.split(&last_year_heading_pattern).collect();
|
||||||
"{}{}\n* [[{}]]{}",
|
page_content = format!(
|
||||||
site_content_above_year_heading,
|
"{}=== {} ===\n* [[{}]]\n\n{}{}",
|
||||||
year_pattern,
|
parts[0], current_year, new_page_title_to_link_to,
|
||||||
new_page_title_to_link_to,
|
last_year_heading_pattern,
|
||||||
site_content_below_year_heading);
|
parts[1]
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
println!("There is neither a {}, nor a {} heading on the main plenum Page!", current_year, last_year);
|
// Fallback: add the current year heading to the end of the page
|
||||||
panic!("The Plenum Main Page seems seriously damaged, aborting...")
|
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(())
|
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`)
|
/// 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>> {
|
pub fn get_page_content(&self, page_title: &str) -> Result<String, Box<dyn Error>> {
|
||||||
let url =
|
let url = format!("{}/api.php?", self.server_url);
|
||||||
format!("{}/api.php?", self.server_url);
|
|
||||||
let params: Box<[(&str, &str)]> = Box::from([
|
let params: Box<[(&str, &str)]> = Box::from([
|
||||||
("action", "parse"), // Create and edit pages.
|
("action", "parse"),
|
||||||
("prop", "wikitext"),
|
("prop", "wikitext"),
|
||||||
("format", "json"),
|
("format", "json"),
|
||||||
("page", page_title),
|
("page", page_title),
|
||||||
|
@ -273,26 +289,118 @@ impl Mediawiki {
|
||||||
]);
|
]);
|
||||||
let resp = self.make_request(url, params, ValidRequestTypes::Get)?;
|
let resp = self.make_request(url, params, ValidRequestTypes::Get)?;
|
||||||
let response_deserialized: serde_json::Value = serde_json::from_str(&resp)?;
|
let response_deserialized: serde_json::Value = serde_json::from_str(&resp)?;
|
||||||
|
|
||||||
let resp = response_deserialized
|
let wikitext = response_deserialized
|
||||||
.get("parse")
|
.get("parse")
|
||||||
.and_then(|parse | parse.get("wikitext"))
|
.and_then(|parse| parse.get("wikitext"))
|
||||||
.ok_or("Expected field `wikitext` not found, likely no access to main plenum page")?;
|
.and_then(|text| text.as_str())
|
||||||
Ok(resp.to_string())
|
.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
|
/// This function runs at the end of do_protocol() and is responsible for
|
||||||
/// logging in to mediawiki, retrieving the necessary tokens, creating a
|
/// logging in to mediawiki, retrieving the necessary tokens, creating a
|
||||||
/// new wiki page for the current plenum protocol, and for linking the new
|
/// new wiki page for the current plenum protocol, and for linking the new
|
||||||
/// page to the main plenum overview page.
|
/// 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
|
// Login to Wiki and get required tokens for logging in and writing
|
||||||
wiki.get_login_token()?;
|
wiki.get_login_token()?;
|
||||||
verboseln!("Login token acquired.");
|
verboseln!("Login token acquired.");
|
||||||
|
|
||||||
let login_result = wiki.login()?;
|
let login_result = wiki.login()?;
|
||||||
verboseln!("Login done.");
|
verboseln!("Login done.");
|
||||||
trace_var!(login_result);
|
trace_var!(login_result);
|
||||||
|
|
||||||
wiki.get_csrf_token()?;
|
wiki.get_csrf_token()?;
|
||||||
verboseln!("CSRF token acquired.");
|
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);
|
trace_var!(pad_converted);
|
||||||
|
|
||||||
// Create a new wiki page plenum_main_page_name/page_title, e.g. under Plenum/13._August_2024
|
// 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");
|
verboseln!("wiki: uploading converted pad");
|
||||||
// let page_title = "14._August_2024";
|
let page_title = create_page_title(date);
|
||||||
let page_title = "14._AUGUST_2024";
|
let full_page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title);
|
||||||
let page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title);
|
|
||||||
wiki.new_wiki_page(&page_title, &pad_converted, true)?;
|
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");
|
verboseln!("Finished successfully with wiki");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -332,14 +435,10 @@ fn pandoc_convert(markdown: String) -> Result<String, Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fn create_page_title(date: &NaiveDate) -> String {
|
||||||
fn create_title (nächster_plenumstermin: String) {
|
date.format("%d. %B %Y").to_string()
|
||||||
let date_simple = NaiveDate::from(nächster_plenumstermin);
|
|
||||||
let wiki_page_title = format!("{} {} {}", date_simple.day(), LongMonthName[date_simple.month()], date_simple.year());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Deserialization must be done this way because the response contains
|
/// Deserialization must be done this way because the response contains
|
||||||
/// two `\\` characters in both the login and csrf tokens, which breaks the
|
/// two `\\` characters in both the login and csrf tokens, which breaks the
|
||||||
/// usual deserialization
|
/// usual deserialization
|
||||||
|
|
Loading…
Reference in a new issue