mediawiki finally working properly, e-mail messages fixed, ran rustfmt
This commit is contained in:
parent
9583d9c37d
commit
7023e9445f
|
@ -59,20 +59,27 @@ impl HedgeDoc {
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Failed to connect to hedgedoc server: HTTP status code {}", response.status()).into())
|
Err(format!(
|
||||||
|
"Failed to connect to hedgedoc server: HTTP status code {}",
|
||||||
|
response.status()
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.is_connect() {
|
if e.is_connect() {
|
||||||
Err("Failed to connect to hedgedoc server. Please check your internet connection or the server URL.".into())
|
Err("Failed to connect to hedgedoc server. Please check your internet connection or the server URL.".into())
|
||||||
} else {
|
} else {
|
||||||
Err(format!("An error occurred while sending the request to the hedgedoc server: {}", e).into())
|
Err(format!(
|
||||||
|
"An error occurred while sending the request to the hedgedoc server: {}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_id_from_response(&self, res: Response) -> String {
|
fn get_id_from_response(&self, res: Response) -> String {
|
||||||
res.url().to_string().trim_start_matches(&format!("{}/", self.server_url)).to_string()
|
res.url().to_string().trim_start_matches(&format!("{}/", self.server_url)).to_string()
|
||||||
}
|
}
|
||||||
|
@ -122,7 +129,8 @@ pub fn strip_metadata(pad_content: String) -> String {
|
||||||
let re_yaml = Regex::new(r"(?s)---\s*.*?\s*(?:\.\.\.|---)").unwrap();
|
let re_yaml = Regex::new(r"(?s)---\s*.*?\s*(?:\.\.\.|---)").unwrap();
|
||||||
let pad_content = re_yaml.replace_all(&pad_content, "").to_string();
|
let pad_content = re_yaml.replace_all(&pad_content, "").to_string();
|
||||||
let re_comment = Regex::new(r"(?s)<!--.*?-->").unwrap();
|
let re_comment = Regex::new(r"(?s)<!--.*?-->").unwrap();
|
||||||
re_comment.replace_all(&pad_content, "").to_string()
|
let content_without_comments = re_comment.replace_all(&pad_content, "").to_string();
|
||||||
|
content_without_comments.trim().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn summarize(pad_content: String) -> String {
|
pub fn summarize(pad_content: String) -> String {
|
||||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -7,11 +7,6 @@ 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, 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::config_spec::{self, CfgField, CfgGroup, CfgSpec};
|
||||||
use cccron_lib::date;
|
use cccron_lib::date;
|
||||||
use cccron_lib::email::{self, Email, SimpleEmail};
|
use cccron_lib::email::{self, Email, SimpleEmail};
|
||||||
|
@ -20,6 +15,11 @@ use cccron_lib::is_dry_run;
|
||||||
use cccron_lib::key_value::{KeyValueStore as KV, KeyValueStore};
|
use cccron_lib::key_value::{KeyValueStore as KV, KeyValueStore};
|
||||||
use cccron_lib::mediawiki::{self, Mediawiki};
|
use cccron_lib::mediawiki::{self, Mediawiki};
|
||||||
use cccron_lib::NYI;
|
use cccron_lib::NYI;
|
||||||
|
use cccron_lib::{trace_var, trace_var_, verboseln};
|
||||||
|
use chrono::{DateTime, Local, NaiveDate, Utc};
|
||||||
|
use clap::{Arg, Command};
|
||||||
|
use colored::Colorize;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
/* ***** Config Spec ***** */
|
/* ***** Config Spec ***** */
|
||||||
const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
|
const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
|
||||||
|
@ -93,12 +93,12 @@ fn today() -> NaiveDate {
|
||||||
}
|
}
|
||||||
/// Gets either the state from the config or overrides it with the state from
|
/// Gets either the state from the config or overrides it with the state from
|
||||||
/// the environment variable `STATE_OVERRIDE` (for testing purposes.)
|
/// the environment variable `STATE_OVERRIDE` (for testing purposes.)
|
||||||
///
|
///
|
||||||
/// For example, `STATE_OVERRIDE=Waiting` can be used to debug mediawiki.
|
/// For example, `STATE_OVERRIDE=Waiting` can be used to debug mediawiki.
|
||||||
fn state(config: &KeyValueStore) -> ProgramState {
|
fn state(config: &KeyValueStore) -> ProgramState {
|
||||||
match env::var("STATE_OVERRIDE") {
|
match env::var("STATE_OVERRIDE") {
|
||||||
Ok(val) => ProgramState::parse(&val),
|
Ok(val) => ProgramState::parse(&val),
|
||||||
Err(_e) => ProgramState::parse(&config["state-name"])
|
Err(_e) => ProgramState::parse(&config["state-name"]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
&config["wiki-http-password"],
|
&config["wiki-http-password"],
|
||||||
&config["wiki-api-user"],
|
&config["wiki-api-user"],
|
||||||
&config["wiki-api-secret"],
|
&config["wiki-api-secret"],
|
||||||
false, // is_dry_run(), // TODO: Remove this false in order for actually letting dry_run affecting if mediawiki is run
|
false, // is_dry_run(), // TODO: Remove this false in order for actually letting dry_run affecting if mediawiki is run
|
||||||
&config["wiki-plenum-page"],
|
&config["wiki-plenum-page"],
|
||||||
);
|
);
|
||||||
trace_var_!(wiki);
|
trace_var_!(wiki);
|
||||||
|
@ -475,20 +475,34 @@ fn do_protocol(
|
||||||
let human_date = plenum_day.format("%d.%m.%Y");
|
let human_date = plenum_day.format("%d.%m.%Y");
|
||||||
let pad_content = hedgedoc::strip_metadata(pad_content);
|
let pad_content = hedgedoc::strip_metadata(pad_content);
|
||||||
let subject = format!("Protokoll vom Plenum am {human_date}");
|
let subject = format!("Protokoll vom Plenum am {human_date}");
|
||||||
NYI!("link for next plenum");
|
let pad_content = pad_content.replace("[toc]", &toc);
|
||||||
NYI!("replace [toc] with actual table of contents");
|
|
||||||
let body = format!(
|
let body = format!(
|
||||||
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
|
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
|
||||||
Das Pad für das nächste Plenum ist zu finden unter {}.\n\n-----\n\n{pad_content}",
|
Das Pad für das nächste Plenum ist zu finden unter <{}/{}>.\nDie Protokolle der letzten Plena findet ihr im wiki unter <{}/index.php?title={}>.\n\n---Protokoll:---\n{pad_content}\n-----",
|
||||||
"<FIXME>"
|
&config["hedgedoc-server-url"],
|
||||||
|
&config["hedgedoc-next-id"],
|
||||||
|
&config["wiki-server-url"],
|
||||||
|
&config["wiki-plenum-page"]
|
||||||
);
|
);
|
||||||
let _message_id = send_email(&subject, &body, email, config)?;
|
let _message_id = send_email(&subject, &body, email, config)?;
|
||||||
NYI!("convert to mediawiki");
|
|
||||||
mediawiki::pad_ins_wiki(pad_content, wiki, plenum_day)?;
|
mediawiki::pad_ins_wiki(pad_content, wiki, plenum_day)?;
|
||||||
NYI!("add to wiki");
|
|
||||||
config.set("state-name", &ProgramState::Logged.to_string()).ok();
|
config.set("state-name", &ProgramState::Logged.to_string()).ok();
|
||||||
} else {
|
} else {
|
||||||
NYI!("What do we do in the no topics / no plenum case?");
|
let human_date = plenum_day.format("%d.%m.%Y");
|
||||||
|
let pad_content = hedgedoc::strip_metadata(pad_content);
|
||||||
|
let subject = format!("Protokoll vom ausgefallenem Plenum am {human_date}");
|
||||||
|
let pad_content = pad_content.replace("[toc]", &toc);
|
||||||
|
let body = format!(
|
||||||
|
"Das letzte Plenum hatte Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
|
||||||
|
Das Pad für das nächste Plenum ist zu finden unter {}/{}.\nDie Protokolle der letzten Plena findet ihr im wiki unter {}/index.php?title={}.\n\n---Protokoll:---{pad_content}",
|
||||||
|
&config["hedgedoc-server-url"],
|
||||||
|
&config["hedgedoc-next-id"],
|
||||||
|
&config["wiki-server-url"],
|
||||||
|
&config["wiki-plenum-page"]
|
||||||
|
);
|
||||||
|
let _message_id = send_email(&subject, &body, email, config)?;
|
||||||
|
mediawiki::pad_ins_wiki(pad_content, wiki, plenum_day)?;
|
||||||
|
config.set("state-name", &ProgramState::Logged.to_string()).ok();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -683,5 +697,5 @@ fn display_logo(eta: &str) {
|
||||||
[1;0;31m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[0m
|
[1;0;31m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[0m
|
||||||
"#;
|
"#;
|
||||||
let ansi_art = format!("{ansi_art_pt1}{eta}{ansi_art_pt2}");
|
let ansi_art = format!("{ansi_art_pt1}{eta}{ansi_art_pt2}");
|
||||||
println!("{}", ansi_art );
|
println!("{}", ansi_art);
|
||||||
}
|
}
|
||||||
|
|
355
src/mediawiki.rs
355
src/mediawiki.rs
|
@ -7,8 +7,8 @@ use reqwest::blocking::Client;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::{trace_var, verboseln};
|
|
||||||
use crate::config_spec::{CfgField, CfgGroup};
|
use crate::config_spec::{CfgField, CfgGroup};
|
||||||
|
use crate::{trace_var, verboseln};
|
||||||
|
|
||||||
pub const CONFIG: CfgGroup<'static> = CfgGroup {
|
pub const CONFIG: CfgGroup<'static> = CfgGroup {
|
||||||
name: "wiki",
|
name: "wiki",
|
||||||
|
@ -24,10 +24,7 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup {
|
||||||
default: "cccb-wiki",
|
default: "cccb-wiki",
|
||||||
description: "HTTP basic auth user name.",
|
description: "HTTP basic auth user name.",
|
||||||
},
|
},
|
||||||
CfgField::Password {
|
CfgField::Password { key: "http-password", description: "HTTP basic auth password." },
|
||||||
key: "http-password",
|
|
||||||
description: "HTTP basic auth password."
|
|
||||||
},
|
|
||||||
CfgField::Default {
|
CfgField::Default {
|
||||||
key: "api-user",
|
key: "api-user",
|
||||||
default: "PlenumBot@PlenumBot-PW2",
|
default: "PlenumBot@PlenumBot-PW2",
|
||||||
|
@ -45,8 +42,8 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup {
|
||||||
CfgField::Default {
|
CfgField::Default {
|
||||||
key: "eta",
|
key: "eta",
|
||||||
default: "no ETA, program never ran",
|
default: "no ETA, program never ran",
|
||||||
description: "ETA message for estimating time the program takes."
|
description: "ETA message for estimating time the program takes.",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,9 +78,16 @@ pub enum ValidRequestTypes {
|
||||||
PostForEditing
|
PostForEditing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ValidPageEdits {
|
||||||
|
WithPotentiallyOverriding,
|
||||||
|
WithoutOverriding,
|
||||||
|
ModifyPlenumPageAfterwards_WithoutOverriding
|
||||||
|
}
|
||||||
|
|
||||||
impl Mediawiki {
|
impl Mediawiki {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
server_url: &str, http_auth_user: &str, http_auth_password: &str, api_user: &str, api_secret: &str, is_dry_run: bool, plenum_main_page_name: &str,
|
server_url: &str, http_auth_user: &str, http_auth_password: &str, api_user: &str,
|
||||||
|
api_secret: &str, is_dry_run: bool, plenum_main_page_name: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
server_url: server_url.to_string(),
|
server_url: server_url.to_string(),
|
||||||
|
@ -98,28 +102,29 @@ impl Mediawiki {
|
||||||
client: Client::builder().cookie_store(true).build().unwrap(),
|
client: Client::builder().cookie_store(true).build().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn login (&self) -> Result<(), Box<dyn Error>> {
|
pub fn login(&self) -> Result<(), Box<dyn Error>> {
|
||||||
let url = format!("{}/api.php?", self.server_url);
|
let url = format!("{}/api.php?", self.server_url);
|
||||||
// retrieve login token first
|
// retrieve login token first
|
||||||
let params_0: Box<[(&str, &str)]> = Box::from([
|
let params_0: Box<[(&str, &str)]> = Box::from([
|
||||||
("action", "query"),
|
("action", "query"),
|
||||||
("meta", "tokens"),
|
("meta", "tokens"),
|
||||||
("type", "login"),
|
("type", "login"),
|
||||||
("format", "json")
|
("format", "json"),
|
||||||
]);
|
]);
|
||||||
verboseln!("Login params: {:?}", params_0);
|
verboseln!("Login params: {:?}", params_0);
|
||||||
let resp_0: String = self.make_request(url.clone(), params_0, ValidRequestTypes::Get)?;
|
let resp_0: String = self.make_request(url.clone(), params_0, ValidRequestTypes::Get)?;
|
||||||
verboseln!("Raw response login_0: {}", resp_0.yellow());
|
verboseln!("Raw response login_0: {}", resp_0.yellow());
|
||||||
let resp_0_deserialized: serde_json::Value = serde_json::from_str(&resp_0)?;
|
let resp_0_deserialized: serde_json::Value = serde_json::from_str(&resp_0)?;
|
||||||
verboseln!("login0 deserialized");
|
verboseln!("login0 deserialized");
|
||||||
|
|
||||||
let login_token = resp_0_deserialized
|
let login_token = resp_0_deserialized
|
||||||
.pointer("/query/tokens/logintoken")
|
.pointer("/query/tokens/logintoken")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|token| token.replace("+\\", ""))
|
|
||||||
.ok_or("Login token not found")?;
|
.ok_or("Login token not found")?;
|
||||||
|
|
||||||
self.login_token.set(login_token)?;
|
verboseln!("Value of login token: {login_token}");
|
||||||
|
|
||||||
|
self.login_token.set(login_token.to_string())?;
|
||||||
|
|
||||||
verboseln!("login0 finished");
|
verboseln!("login0 finished");
|
||||||
let login_token: String = self.login_token.clone().into_inner().unwrap();
|
let login_token: String = self.login_token.clone().into_inner().unwrap();
|
||||||
|
@ -129,15 +134,16 @@ impl Mediawiki {
|
||||||
("lgname", &self.api_user),
|
("lgname", &self.api_user),
|
||||||
("lgpassword", &self.api_secret),
|
("lgpassword", &self.api_secret),
|
||||||
("lgtoken", &login_token),
|
("lgtoken", &login_token),
|
||||||
("format", "json")
|
("format", "json"),
|
||||||
]);
|
]);
|
||||||
verboseln!("Login params: {:?}", params_1);
|
verboseln!("Login params: {:?}", params_1);
|
||||||
let resp_1: String = self.client
|
let resp_1: String = self
|
||||||
.post(&url)
|
.client
|
||||||
// .basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
.post(&url)
|
||||||
.form(¶ms_1)
|
// .basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
||||||
.send()?
|
.form(¶ms_1)
|
||||||
.text()?;
|
.send()?
|
||||||
|
.text()?;
|
||||||
verboseln!("Raw response login_1: {}", resp_1.yellow());
|
verboseln!("Raw response login_1: {}", resp_1.yellow());
|
||||||
let resp_1_deserialized: serde_json::Value = serde_json::from_str(&resp_1)?;
|
let resp_1_deserialized: serde_json::Value = serde_json::from_str(&resp_1)?;
|
||||||
if let Some(result) = resp_1_deserialized.get("login").and_then(|l| l.get("result")) {
|
if let Some(result) = resp_1_deserialized.get("login").and_then(|l| l.get("result")) {
|
||||||
|
@ -151,14 +157,10 @@ impl Mediawiki {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_csrf_token(&self) -> Result<(), Box<dyn Error>> {
|
pub fn get_csrf_token(&self) -> Result<(), Box<dyn Error>> {
|
||||||
let url =
|
let url = format!("{}/api.php?", self.server_url);
|
||||||
format!("{}/api.php?", self.server_url);
|
let params: Box<[(&str, &str)]> =
|
||||||
let params: Box<[(&str, &str)]> = Box::from([
|
Box::from([("format", "json"), ("meta", "tokens"), ("action", "query")]);
|
||||||
("format", "json"),
|
let resp: String = self.make_request(url, params, ValidRequestTypes::Get)?;
|
||||||
("meta", "tokens"),
|
|
||||||
("action", "query")
|
|
||||||
]);
|
|
||||||
let resp: String = self.make_request(url, params, ValidRequestTypes::Get)?;
|
|
||||||
verboseln!("Raw response csrf: {}", resp);
|
verboseln!("Raw response csrf: {}", resp);
|
||||||
let response_deserialized: QueryResponseCsrf = serde_json::from_str(&resp)?;
|
let response_deserialized: QueryResponseCsrf = serde_json::from_str(&resp)?;
|
||||||
let token = response_deserialized.query.tokens.csrftoken;
|
let token = response_deserialized.query.tokens.csrftoken;
|
||||||
|
@ -171,144 +173,209 @@ impl Mediawiki {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_request(&self, url: String, params: Box<[(&str, &str)]>, request_type: ValidRequestTypes) -> Result<String, Box<dyn Error>> {
|
pub fn make_request(
|
||||||
let resp: Result<String, Box<dyn Error>> = match
|
&self, url: String, params: Box<[(&str, &str)]>, request_type: ValidRequestTypes,
|
||||||
match request_type {
|
) -> Result<String, Box<dyn Error>> {
|
||||||
ValidRequestTypes::Get => {
|
let resp: Result<String, Box<dyn Error>> = match match request_type {
|
||||||
self
|
ValidRequestTypes::Get => {
|
||||||
.client
|
self.client
|
||||||
.get(url)
|
.get(url)
|
||||||
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
||||||
.query(¶ms)
|
.query(¶ms)
|
||||||
.send()
|
.send()
|
||||||
}
|
},
|
||||||
ValidRequestTypes::Post | ValidRequestTypes::PostForEditing => {
|
ValidRequestTypes::Post | ValidRequestTypes::PostForEditing => {
|
||||||
// convert the params into a HashMap for JSON
|
// convert the params into a HashMap for JSON
|
||||||
let params_map: std::collections::HashMap<_, _> = params.iter().cloned().collect();
|
let params_map: std::collections::HashMap<_, _> = params.iter().cloned().collect();
|
||||||
self
|
self.client
|
||||||
.client
|
.post(url)
|
||||||
.post(url)
|
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
||||||
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
|
.form(¶ms_map)
|
||||||
.query(¶ms_map)
|
.send()
|
||||||
.send()
|
},
|
||||||
}
|
} {
|
||||||
}
|
|
||||||
{
|
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
match request_type {
|
match request_type {
|
||||||
ValidRequestTypes::PostForEditing => Ok(response.text()?),
|
ValidRequestTypes::PostForEditing => Ok(response.text()?),
|
||||||
_ => Ok(response.text()?)
|
_ => Ok(response.text()?),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"Failed to connect to wiki server: HTTP status code {}",
|
||||||
|
response.status()
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
else {
|
},
|
||||||
Err(format!("Failed to connect to wiki server: HTTP status code {}", response.status()).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.is_connect() {
|
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())
|
Err(format!("Failed to connect to wiki server. Please check your internet connection or the server URL.\n(Error: {})", e).into())
|
||||||
} else {
|
} else {
|
||||||
Err(format!("An error occurred while sending the request to the wiki server: {}", e).into())
|
Err(format!(
|
||||||
|
"An error occurred while sending the request to the wiki server: {}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 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: ValidPageEdits,
|
||||||
|
) -> Result<String, Box<dyn Error>> {
|
||||||
// Prevent dry run from making actual wiki edits
|
// Prevent dry run from making actual wiki edits
|
||||||
if self.is_dry_run {
|
if self.is_dry_run {
|
||||||
println!("Dry run: Would create wiki page '{}' with content {}", page_title, page_content);
|
println!(
|
||||||
|
"Dry run: Would create wiki page '{}' with content {}",
|
||||||
|
page_title, page_content
|
||||||
|
);
|
||||||
return Ok("Dry run - no actual page created".to_string());
|
return Ok("Dry run - no actual page created".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have a CSRF token
|
// Ensure we have a CSRF token
|
||||||
if self.csrf_token.get().is_none() {
|
if self.csrf_token.get().is_none() {
|
||||||
return Err("CSRF token not set. Call get_csrf_token() first.".into());
|
return Err("CSRF token not set. Call get_csrf_token() first.".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = format!("{}/api.php", self.server_url);
|
let url = format!("{}/api.php", self.server_url);
|
||||||
let params: Box<[(&str, &str)]> = Box::from([
|
|
||||||
("action", "edit"),
|
let params: Box<[(&str, &str)]> = match update_main_page {
|
||||||
("format", "json"),
|
ValidPageEdits::WithPotentiallyOverriding => {
|
||||||
("title", page_title),
|
// This means we *EDIT* the *Main Page* and do not prevent overwriting
|
||||||
("text", page_content),
|
Box::from([
|
||||||
("token", self.csrf_token.get().unwrap()),
|
("action", "edit"),
|
||||||
("createonly", "true"), // Prevent overwriting existing pages
|
("format", "json"),
|
||||||
("bot", "true"),
|
("title", page_title),
|
||||||
]);
|
("text", page_content),
|
||||||
|
("token", self.csrf_token.get().unwrap()),
|
||||||
|
("bot", "true")
|
||||||
|
])
|
||||||
|
}
|
||||||
|
ValidPageEdits::ModifyPlenumPageAfterwards_WithoutOverriding => {
|
||||||
|
// This means we *CREATE* a *new Page* and always prevent overwriting
|
||||||
|
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"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
ValidPageEdits::WithoutOverriding => {
|
||||||
|
// This means we *CREATE* a *new Page* and always prevent overwriting
|
||||||
|
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"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verboseln!("Current page title: {page_title}");
|
||||||
// Log the request details for debugging
|
// Log the request details for debugging
|
||||||
verboseln!("Creating wiki page: {} at {}", page_title, url);
|
verboseln!("Creating wiki page: {} at {}", page_title, url);
|
||||||
|
|
||||||
// Make the request to create the page
|
// Make the request to create the page
|
||||||
let request_result = self.make_request(url, params, ValidRequestTypes::PostForEditing)?;
|
let request_result: String =
|
||||||
|
self.make_request(url, params, ValidRequestTypes::PostForEditing)?;
|
||||||
|
verboseln!("pos1");
|
||||||
// Parse the response to check for success
|
// Parse the response to check for success
|
||||||
let response: serde_json::Value = serde_json::from_str(&request_result)?;
|
let response_result = serde_json::from_str::<serde_json::Value>(&request_result);
|
||||||
|
let response = response_result.unwrap_or_else(|e| {
|
||||||
|
print!("Error while creating new wiki page:\n{}", e.to_string().cyan());
|
||||||
|
return serde_json::from_str("\"(error)\"").unwrap()
|
||||||
|
});
|
||||||
|
verboseln!("pos2");
|
||||||
// Check if the page creation was successful
|
// Check if the page creation was successful
|
||||||
if let Some(edit) = response.get("edit") {
|
if let Some(edit) = response.get("edit") {
|
||||||
|
verboseln!("pos3");
|
||||||
if edit.get("result").and_then(|r| r.as_str()) == Some("Success") {
|
if edit.get("result").and_then(|r| r.as_str()) == Some("Success") {
|
||||||
verboseln!("Successfully created wiki page: {}", page_title);
|
verboseln!("Successfully created wiki page: {}", page_title);
|
||||||
|
verboseln!("pos4");
|
||||||
// Update the main plenum page if requested
|
|
||||||
if update_main_page {
|
|
||||||
self.update_plenum_page(page_title)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(request_result)
|
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Failed to create wiki page. Response: {}", response).into())
|
print!("Failed to create wiki page. Response: {response}");
|
||||||
|
verboseln!("pos5");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Unexpected response when creating wiki page: {}", response).into())
|
print!("Unexpected response when creating wiki page: {response}");
|
||||||
}
|
verboseln!("pos6");
|
||||||
|
};
|
||||||
|
verboseln!("pos7");
|
||||||
|
// Update the main plenum page if requested
|
||||||
|
match update_main_page {
|
||||||
|
ValidPageEdits::ModifyPlenumPageAfterwards_WithoutOverriding => {
|
||||||
|
verboseln!("updating main page...");
|
||||||
|
self.update_plenum_page(page_title)?
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
};
|
||||||
|
return Ok(page_title.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>> {
|
||||||
let current_year = Utc::now().year().to_string();
|
let current_year = Utc::now().year().to_string();
|
||||||
let year_heading_pattern = format!("=== {} ===", current_year);
|
let year_heading_pattern = format!("=== {} ===", current_year);
|
||||||
|
|
||||||
// Download Plenum page content
|
// Download Plenum page content
|
||||||
let mut page_content = self.get_page_content(&self.plenum_main_page_name)?;
|
let mut page_content = self.get_page_content(&self.plenum_main_page_name)?;
|
||||||
|
|
||||||
// check if the current year heading pattern exists
|
// check first if the script has been run before and if the link already exists
|
||||||
if !page_content.contains(&year_heading_pattern) {
|
if !page_content.contains(&new_page_title_to_link_to) {
|
||||||
// If not, add a new year heading pattern
|
|
||||||
let last_year = (current_year.parse::<i32>()? - 1).to_string();
|
// check if the current year heading pattern exists
|
||||||
let last_year_heading_pattern = format!("=== {} ===", last_year);
|
if !page_content.contains(&year_heading_pattern) {
|
||||||
|
// If not, add a new year heading pattern
|
||||||
if page_content.contains(&last_year_heading_pattern) {
|
let last_year = (current_year.parse::<i32>()? - 1).to_string();
|
||||||
// add a new year heading pattern before the last year one
|
let last_year_heading_pattern = format!("=== {} ===", last_year);
|
||||||
let parts: Vec<&str> = page_content.split(&last_year_heading_pattern).collect();
|
|
||||||
page_content = format!(
|
if page_content.contains(&last_year_heading_pattern) {
|
||||||
"{}=== {} ===\n* [[{}]]\n\n{}{}",
|
// add a new year heading pattern before the last year one
|
||||||
parts[0], current_year, new_page_title_to_link_to,
|
let parts: Vec<&str> = page_content.split(&last_year_heading_pattern).collect();
|
||||||
last_year_heading_pattern,
|
page_content = format!(
|
||||||
parts[1]
|
"{}=== {} ===\n* [[{}]]\n\n{}{}",
|
||||||
);
|
parts[0],
|
||||||
|
current_year,
|
||||||
|
new_page_title_to_link_to,
|
||||||
|
last_year_heading_pattern,
|
||||||
|
parts[1]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 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 {
|
} else {
|
||||||
// Fallback: add the current year heading to the end of the page
|
// Paste the link below the current year heading
|
||||||
page_content.push_str(&format!("\n\n=== {} ===\n* [[{}]]", current_year, new_page_title_to_link_to));
|
page_content = page_content.replace(
|
||||||
|
&year_heading_pattern,
|
||||||
|
&format!("{}\n* [[{}]]", year_heading_pattern, new_page_title_to_link_to),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Paste the link below the current year heading
|
verboseln!("{}", "The bot appears to have been run before and a duplicate link to the new plenum pad was avoided.".yellow())
|
||||||
page_content = page_content.replace(
|
|
||||||
&year_heading_pattern,
|
|
||||||
&format!("{}\n* [[{}]]", year_heading_pattern, new_page_title_to_link_to)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh page
|
// refresh page
|
||||||
self.new_wiki_page(&self.plenum_main_page_name, &page_content, false)?;
|
self.new_wiki_page(&self.plenum_main_page_name, &page_content, ValidPageEdits::WithPotentiallyOverriding)?;
|
||||||
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`)
|
||||||
|
@ -323,33 +390,31 @@ 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 wikitext = response_deserialized
|
let wikitext = response_deserialized
|
||||||
.get("parse")
|
.get("parse")
|
||||||
.and_then(|parse| parse.get("wikitext"))
|
.and_then(|parse| parse.get("wikitext"))
|
||||||
.and_then(|text| text.as_str())
|
.and_then(|text| text.as_str())
|
||||||
.ok_or("Expected field `wikitext` not found")?;
|
.ok_or("Expected field `wikitext` not found")?;
|
||||||
|
|
||||||
Ok(wikitext.to_string())
|
Ok(wikitext.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_wiki_write(&self) -> Result<(), Box<dyn Error>> {
|
pub fn test_wiki_write(&self) -> Result<(), Box<dyn Error>> {
|
||||||
// Generate a unique test page title
|
// Generate a unique test page title
|
||||||
let test_page_title = format!(
|
let test_page_title =
|
||||||
"TestPage/WikiWriteTest-{}",
|
format!("TestPage/WikiWriteTest-{}", chrono::Utc::now().format("%Y%m%d%H%M%S"));
|
||||||
chrono::Utc::now().format("%Y%m%d%H%M%S")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test content to write
|
// Test content to write
|
||||||
let test_content = format!(
|
let test_content = format!(
|
||||||
"Wiki Write Test\n\nThis is a test page generated at {}. \nIt can be safely deleted.",
|
"Wiki Write Test\n\nThis is a test page generated at {}. \nIt can be safely deleted.",
|
||||||
chrono::Utc::now()
|
chrono::Utc::now()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure login token and CSRF token are set
|
// Ensure login token and CSRF token are set
|
||||||
let _login_result = self.login()?;
|
let _login_result = self.login()?;
|
||||||
self.get_csrf_token()?;
|
self.get_csrf_token()?;
|
||||||
|
|
||||||
let url = format!("{}/api.php", self.server_url);
|
let url = format!("{}/api.php", self.server_url);
|
||||||
let params: Box<[(&str, &str)]> = Box::from([
|
let params: Box<[(&str, &str)]> = Box::from([
|
||||||
("action", "edit"),
|
("action", "edit"),
|
||||||
|
@ -360,25 +425,26 @@ impl Mediawiki {
|
||||||
("createonly", "true"),
|
("createonly", "true"),
|
||||||
("bot", "true"),
|
("bot", "true"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Manually print out all parameters for debugging
|
// Manually print out all parameters for debugging
|
||||||
println!("Debug - URL: {}", url);
|
println!("Debug - URL: {}", url);
|
||||||
println!("Debug - Parameters:");
|
println!("Debug - Parameters:");
|
||||||
for (key, value) in params.iter() {
|
for (key, value) in params.iter() {
|
||||||
println!(" {}: {}", key, value);
|
println!(" {}: {}", key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make request and capture the full response
|
// Make request and capture the full response
|
||||||
let request_result = match self.client
|
let request_result = match self
|
||||||
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.basic_auth(&self.http_user, Some(&self.http_password))
|
.basic_auth(&self.http_user, Some(&self.http_password))
|
||||||
.form(¶ms)
|
.form(¶ms)
|
||||||
.send()
|
.send()
|
||||||
{
|
{
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
println!("Debug - Response Status: {}", response.status());
|
println!("Debug - Response Status: {}", response.status());
|
||||||
println!("Debug - Response Headers: {:?}", response.headers());
|
println!("Debug - Response Headers: {:?}", response.headers());
|
||||||
|
|
||||||
match response.text() {
|
match response.text() {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
println!("Debug - Raw Response Body:\n{}", text);
|
println!("Debug - Raw Response Body:\n{}", text);
|
||||||
|
@ -386,19 +452,19 @@ impl Mediawiki {
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(format!("Failed to read response body: {}", e).into());
|
return Err(format!("Failed to read response body: {}", e).into());
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(format!("Request failed: {}", e).into());
|
return Err(format!("Request failed: {}", e).into());
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attempt to parse the response
|
// Attempt to parse the response
|
||||||
match serde_json::from_str::<serde_json::Value>(&request_result) {
|
match serde_json::from_str::<serde_json::Value>(&request_result) {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
println!("Debug - Parsed Response: {}", response);
|
println!("Debug - Parsed Response: {}", response);
|
||||||
|
|
||||||
if let Some(edit) = response.get("edit") {
|
if let Some(edit) = response.get("edit") {
|
||||||
if edit.get("result").and_then(|r| r.as_str()) == Some("Success") {
|
if edit.get("result").and_then(|r| r.as_str()) == Some("Success") {
|
||||||
println!("✅ Successfully created test page: {}", test_page_title);
|
println!("✅ Successfully created test page: {}", test_page_title);
|
||||||
|
@ -409,10 +475,11 @@ impl Mediawiki {
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Unexpected response: {}", response).into())
|
Err(format!("Unexpected response: {}", response).into())
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Err(format!("JSON parsing failed. Error: {}. Raw response: {}", e, request_result).into())
|
Err(format!("JSON parsing failed. Error: {}. Raw response: {}", e, request_result)
|
||||||
}
|
.into())
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -421,29 +488,31 @@ impl Mediawiki {
|
||||||
/// 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, plenum_date: &NaiveDate) -> 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
|
// Use the provided date or default to current date
|
||||||
let date = plenum_date;
|
let date = plenum_date;
|
||||||
wiki.test_wiki_write()?;
|
// 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
|
||||||
verboseln!("logging in...");
|
verboseln!("logging in...");
|
||||||
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.");
|
||||||
|
|
||||||
// Convert to mediawiki and make new page
|
// Convert to mediawiki and make new page
|
||||||
let pad_converted = pandoc_convert(old_pad_content)?;
|
let pad_converted = pandoc_convert(old_pad_content)?;
|
||||||
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
|
||||||
verboseln!("wiki: uploading converted pad");
|
verboseln!("wiki: uploading converted pad");
|
||||||
let page_title = create_page_title(date);
|
let page_title = create_page_title(date);
|
||||||
let full_page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title);
|
let full_page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title);
|
||||||
|
|
||||||
wiki.new_wiki_page(&full_page_title, &pad_converted, true)?;
|
wiki.new_wiki_page(&full_page_title, &pad_converted, ValidPageEdits::ModifyPlenumPageAfterwards_WithoutOverriding)?;
|
||||||
|
|
||||||
verboseln!("Finished successfully with wiki");
|
verboseln!("Finished successfully with wiki");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue