
- Updated new_wiki_page - New update_plenum_page function to download the main Plenum Page from Mediawiki, insert the Link to the new Page and replace the content of the mediawiki - new get-page functions - new edit-selection functions (Wiki still in progress)
400 lines
15 KiB
Rust
400 lines
15 KiB
Rust
use std::error::Error;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
|
|
use colored::Colorize;
|
|
use pandoc::{PandocError, PandocOutput};
|
|
use reqwest::blocking::Client;
|
|
use serde::Deserialize;
|
|
|
|
use crate::config_spec::{CfgField, CfgGroup};
|
|
|
|
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-PW2",
|
|
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.",
|
|
},
|
|
CfgField::Default {
|
|
key: "plenum-page",
|
|
default: "Plenum",
|
|
description: "The name of the wiki page where all new plenum pages will be linked.",
|
|
},
|
|
CfgField::Default {
|
|
key: "eta",
|
|
default: "no ETA, program never ran",
|
|
description: "ETA message for estimating time the program takes."
|
|
}
|
|
],
|
|
};
|
|
|
|
pub struct Mediawiki {
|
|
server_url: String,
|
|
http_user: String,
|
|
http_password: String,
|
|
api_user: String,
|
|
api_secret: String,
|
|
is_dry_run: bool,
|
|
login_token: String,
|
|
csrf_token: String,
|
|
plenum_main_page_name: String,
|
|
client: Client,
|
|
}
|
|
|
|
impl std::fmt::Debug for Mediawiki {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Mediawiki")
|
|
.field("server_url", &self.server_url)
|
|
.field("http_user", &self.http_user)
|
|
.field("http_password", &"*****")
|
|
.field("is_dry_run", &self.is_dry_run)
|
|
.field("client", &self.client)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
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, plenum_main_page_name: &str,
|
|
) -> Self {
|
|
Self {
|
|
server_url: server_url.to_string(),
|
|
http_user: http_auth_user.to_string(),
|
|
http_password: http_auth_password.to_string(),
|
|
api_user: api_user.to_string(),
|
|
api_secret: api_secret.to_string(),
|
|
is_dry_run,
|
|
login_token: String::new(),
|
|
csrf_token: String::new(),
|
|
plenum_main_page_name: plenum_main_page_name.to_string(),
|
|
client: Client::builder().cookie_store(true).build().unwrap(),
|
|
}
|
|
}
|
|
pub fn get_login_token(&self) -> Result<String, Box<dyn Error>> {
|
|
let url =
|
|
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<String, Box<dyn Error>> {
|
|
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<String, Box<dyn Error>> = self.make_request(url, params, RequestType::Post);
|
|
Ok(resp.unwrap())
|
|
}
|
|
pub fn get_csrf_token(&self) -> Result<String, Box<dyn Error>> { // HAS TO BE FIXED
|
|
let url =
|
|
format!("{}/api.php?", self.server_url);
|
|
let params: Box<[(&str, &str)]> = Box::from([
|
|
("format", "json"),
|
|
("meta", "tokens"),
|
|
("formatversion", "2"),
|
|
("action", "query")
|
|
]);
|
|
let resp: Result<String, Box<dyn Error>> = 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<String, Box<dyn Error>> {
|
|
let resp: Result<String, Box<dyn Error>> = 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<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.as_str()), // A "csrf" token retrieved from action=query&meta=tokens
|
|
("bot", "true"), // Mark this edit as a bot edit.
|
|
]);
|
|
let request_result = self.make_request(url, params, RequestType::Post);
|
|
|
|
self.update_plenum_page(page_title)?;
|
|
|
|
request_result
|
|
}
|
|
/// Downloads the main Plenum Page from Mediawiki, inserts the Link to the new Page and replaces the content of the mediawiki-
|
|
pub fn update_plenum_page (&self, new_page_title_to_link_to: &str) -> Result<(), Box<dyn Error>> {
|
|
// 1. Download Plenum page content
|
|
let page_content = self.get_page_content(&self.plenum_main_page_name)?;
|
|
println!("---\nPage Content: {}\n---", page_content.red());
|
|
let current_year = "2024"; // TODO: Datumslogik einbauen
|
|
let year_section = format!("=== {} ===\n", current_year);
|
|
if page_content.contains(&year_section) {
|
|
let mut content_split: Vec<&str> = page_content.split(&year_section).collect();
|
|
println!("Length: {}", content_split.len());
|
|
let rest_of_content = content_split.pop().unwrap_or_default();
|
|
let updated_section = format!("{}{}\n* {}", content_split.join(&year_section), year_section, new_page_title_to_link_to);
|
|
//format!("{}{}", updated_section, rest_of_content)
|
|
}
|
|
Ok(())
|
|
}
|
|
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.
|
|
("prop", "wikitext"),
|
|
("format", "json"),
|
|
("page", page_title),
|
|
("formatversion", "2"),
|
|
]);
|
|
let resp: Result<String, Box<dyn Error>> = self.make_request(url, params, RequestType::Get);
|
|
let resp = resp?;
|
|
let response_deserialized: ParseResponse = serde_json::from_str(&resp)?;
|
|
Ok(response_deserialized.parse.wikitext)
|
|
}
|
|
pub fn get_page_section_title (&self, page_title: &str, section_number: &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.
|
|
("contentmodel", "wikitext"),
|
|
("format", "json"),
|
|
("page", page_title),
|
|
]);
|
|
let resp: Result<String, Box<dyn Error>> = self.make_request(url, params, RequestType::Get);
|
|
let resp = resp?;
|
|
todo!()
|
|
//let response_deserialized = serde_json::from_str(&resp)?;
|
|
//Ok(response_deserialized["parse"])
|
|
}
|
|
pub fn edit_section (&self, page_title: &str, text_to_prepend: &str, section_number: &str) -> Result<String, Box<dyn Error>> {
|
|
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.
|
|
("section", section_number), // Section identifier. 0 for the top section, new for a new section. Often a positive integer, but can also be non-numeric
|
|
("prependtext", text_to_prepend), // 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.
|
|
]);
|
|
let request_result = self.make_request(url, params, RequestType::Post);
|
|
|
|
request_result
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct QueryResponseLogin {
|
|
batchcomplete: String,
|
|
query: QueryTokensLogin,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct QueryTokensLogin {
|
|
tokens: TokensLogin,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct TokensLogin {
|
|
logintoken: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct QueryResponseCsrf {
|
|
batchcomplete: bool,
|
|
query: crate::mediawiki::QueryTokensCsrf,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct QueryTokensCsrf {
|
|
tokens: crate::mediawiki::TokensCsrf,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct TokensCsrf {
|
|
csrftoken: String,
|
|
}
|
|
// For get_page_content:
|
|
#[derive(Deserialize)]
|
|
struct ParseResponse {
|
|
parse: ParseContent,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct ParseContent {
|
|
wikitext: String,
|
|
}
|
|
// For get_page_section:
|
|
/*
|
|
#[derive(Deserialize)]
|
|
struct ParseSectionResponse {
|
|
parse: ParseSectionContent,
|
|
}
|
|
#[derive(Deserialize)]
|
|
struct ParseSectionContent {
|
|
sections: ,
|
|
}
|
|
|
|
*/
|
|
|
|
pub fn pad_ins_wiki(old_pad_content: String, wiki: &mut Mediawiki) -> Result<(), Box<dyn Error>> {
|
|
// 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);
|
|
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);
|
|
let page_title = "Page Test 5";
|
|
let page_title = format!("{}/{}", wiki.plenum_main_page_name, page_title); // Example: Plenum/13._August_2024
|
|
wiki.new_wiki_page(&page_title, &pad_converted)?;
|
|
|
|
|
|
// Textdatei wieder einlesen
|
|
|
|
// Passwörter aus Datenbank lesen (ToBeDone)
|
|
/*
|
|
let plenum_bot_user = String::from("PlenumBot@PlenumBot-PW1");
|
|
let plenum_bot_pw = String::from("**OLD_API_PW_REMOVED**");
|
|
let login_token = login_to_mediawiki(plenum_bot_user.clone(), plenum_bot_pw.clone())
|
|
.expect("Fehler beim Einloggen!");
|
|
println!("plenum_bot_user: {plenum_bot_user}, plenum_bot_pw: {plenum_bot_pw}, login_token: {login_token}")
|
|
|
|
*/
|
|
Ok(())
|
|
}
|
|
|
|
/// Converts one file type into another using pandoc and saves the result as a txt file
|
|
fn pandoc_convert(
|
|
old_pad_content: String, output_filepath: &str, input_format: pandoc::InputFormat,
|
|
output_format: pandoc::OutputFormat,
|
|
) -> Result<PandocOutput, PandocError> {
|
|
//Convert Markdown into Mediawiki
|
|
// Vanilla pandoc Befehl: pandoc --from markdown --to mediawiki --no-highlight
|
|
let mut p = pandoc::new();
|
|
p.set_input(pandoc::InputKind::Pipe(old_pad_content));
|
|
p.set_input_format(input_format, vec![]);
|
|
p.set_output(pandoc::OutputKind::File(output_filepath.parse().unwrap()));
|
|
p.set_output_format(output_format, vec![]);
|
|
p.execute()
|
|
}
|
|
|
|
|
|
/// 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)
|
|
.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!");
|
|
contents
|
|
}
|
|
|
|
/// Takes a Sting in the Markdown format and returns a String in the mediawiki Format
|
|
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");
|
|
let temp = read_txt_file(output_filepath);
|
|
println!("TEMP: {}", temp.purple());
|
|
temp
|
|
}
|
|
|
|
/*
|
|
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());
|
|
}
|
|
|
|
*/
|