Compare commits

..

10 commits

Author SHA1 Message Date
murmeldin 9583d9c37d removed Cargo.lock and shell.nix from .gitignore 2024-12-10 21:38:35 +01:00
murmeldin 20b8ce40de restructuring of mediawiki (still in progress) 2024-12-10 21:38:35 +01:00
murmeldin 659e9f258a mediawiki work in progress 2024-12-10 21:38:35 +01:00
murmeldin bbd9b2c29e major improvements to plenum pad logic
- a lot of new comments for better doc
- removed unnecessary functions
- main plenum pad logic still in progress
- one additional comment in main
2024-12-10 21:38:35 +01:00
murmeldin aca180d6ba new state() function
- now, it is possible to use e.g. STATE_OVERRIDE=Waiting to override the state
- this is useful for development in order to ignore parts of the config.sqlite
2024-12-10 21:38:35 +01:00
nobody b04d35ee6b mediawiki: more follow-up cleanup
- use macros for conditional status messages
- abstract out a pipe() fn
2024-12-10 21:38:35 +01:00
nobody d3681e1699 mediawiki/pandoc: call directly instead of via lib
Pandoc crate is confusing and badly documented.
Using std::process::command now to avoid temporary files.
2024-12-10 21:38:35 +01:00
nobody 3608838949 mediawiki: use OnceCell instead of mut
login_token and csrf_token are internally mutable now
2024-12-10 21:38:35 +01:00
murmeldin 12450ce5a3 mediawiki mutable in main.rs, less unwrap in mediawiki.rs 2024-12-10 21:38:35 +01:00
Marek Krug 2da4a149c9 mediawiki: new features
- 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)
2024-12-10 21:38:35 +01:00
8 changed files with 3311 additions and 138 deletions

3
.gitignore vendored
View file

@ -1,9 +1,8 @@
/target
/.idea
/src/debug_emails.txt
Cargo.lock
*.sqlite
*.sqlite3
.direnv
shell.nix
pandoc*.txt
.envrc

2896
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,6 @@ edition = "2021"
[dependencies]
stdext = "0.3.3"
pandoc = "0.8"
chrono = "0.4.38"
regex = "1.10.5"
futures = "0.3.30"

3
architecture.md Normal file
View file

@ -0,0 +1,3 @@
# Architecture
This document is intended to explain how the Plenum Bot works.

25
shell.nix Normal file
View file

@ -0,0 +1,25 @@
{pkgs ? import <nixpkgs> {}}: let
rust-toolchain = pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [rustc cargo rustPlatform.rustcSrc rustfmt clippy];
};
in
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [
rust-toolchain
pkg-config
xe
xz
cargo-tarpaulin
openssl
sqlite
gcc
gnumake
# dotnet-sdk_8
];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}

View file

@ -7,6 +7,33 @@ pub mod mediawiki;
pub mod template;
use std::env;
use std::error::Error;
use std::io::{Read, Write};
use std::process::{Command, ExitStatus, Stdio};
fn pipe(
program: &str, args: &mut [&str], input: String,
) -> Result<(String, String, ExitStatus), Box<dyn Error>> {
let mut cmd = Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
if let Some(mut stdin) = cmd.stdin.take() {
stdin.write_all(input.as_bytes())?;
}
let mut output = String::new();
if let Some(mut stdout) = cmd.stdout.take() {
stdout.read_to_string(&mut output)?;
}
let mut errmsg = String::new();
if let Some(mut stderr) = cmd.stderr.take() {
stderr.read_to_string(&mut errmsg)?;
}
let status = cmd.wait()?;
Ok((output, errmsg, status))
}
/// Checks environment variable `DRY_RUN` to see if any external operations
/// should *actually* be done.

View file

@ -2,25 +2,23 @@
use std::env;
use std::error::Error;
use std::fmt::Display;
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 std::fmt::Display;
use std::io::IsTerminal;
use cccron_lib::{trace_var, trace_var_, verboseln};
use cccron_lib::config_spec::{self, CfgField, CfgGroup, CfgSpec};
use cccron_lib::key_value::KeyValueStore as KV;
use cccron_lib::date;
use cccron_lib::email::{self, Email, SimpleEmail};
use cccron_lib::hedgedoc::{self, HedgeDoc};
use cccron_lib::is_dry_run;
use cccron_lib::key_value::{KeyValueStore as KV, KeyValueStore};
use cccron_lib::mediawiki::{self, Mediawiki};
use cccron_lib::{verboseln,trace_var,trace_var_};
use cccron_lib::NYI;
/* ***** Config Spec ***** */
@ -93,6 +91,16 @@ fn today() -> NaiveDate {
.map(|v| NaiveDate::parse_from_str(&v, "%F").expect("'TODAY' hat nicht format YYYY-MM-DD"))
.unwrap_or(Local::now().date_naive())
}
/// Gets either the state from the config or overrides it with the state from
/// the environment variable `STATE_OVERRIDE` (for testing purposes.)
///
/// For example, `STATE_OVERRIDE=Waiting` can be used to debug mediawiki.
fn state(config: &KeyValueStore) -> ProgramState {
match env::var("STATE_OVERRIDE") {
Ok(val) => ProgramState::parse(&val),
Err(_e) => ProgramState::parse(&config["state-name"])
}
}
#[derive(Debug)]
struct Args {
@ -170,7 +178,8 @@ fn main() -> Result<(), Box<dyn Error>> {
&config["wiki-http-password"],
&config["wiki-api-user"],
&config["wiki-api-secret"],
is_dry_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"],
);
trace_var_!(wiki);
// get next plenum days
@ -181,7 +190,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let nearest_plenum_days = date::get_matching_dates_around(today, plenum_spec);
trace_var!(nearest_plenum_days);
// figure out where we are
let mut last_state = ProgramState::parse(&config["state-name"]);
let mut last_state = state(&config);
let last_run = config.get("state-last-run").unwrap_or_default();
let last_run = NaiveDate::parse_from_str(&last_run, "%Y-%m-%d").unwrap_or_default();
trace_var!(last_run);
@ -475,6 +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, plenum_day)?;
NYI!("add to wiki");
config.set("state-name", &ProgramState::Logged.to_string()).ok();
} else {

View file

@ -1,12 +1,13 @@
use std::cell::OnceCell;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use chrono::{Datelike, NaiveDate, Utc};
use colored::Colorize;
use pandoc::{PandocError, PandocOutput};
use reqwest::blocking::Client;
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{trace_var, verboseln};
use crate::config_spec::{CfgField, CfgGroup};
pub const CONFIG: CfgGroup<'static> = CfgGroup {
@ -36,10 +37,15 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup {
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"
description: "ETA message for estimating time the program takes."
}
],
};
@ -51,8 +57,9 @@ pub struct Mediawiki {
api_user: String,
api_secret: String,
is_dry_run: bool,
login_token: String,
csrf_token: String,
login_token: OnceCell<String>,
csrf_token: OnceCell<String>,
plenum_main_page_name: String,
client: Client,
}
@ -68,7 +75,7 @@ impl std::fmt::Debug for Mediawiki {
}
}
pub enum RequestType {
pub enum ValidRequestTypes {
Get,
Post,
PostForEditing
@ -76,7 +83,7 @@ pub enum RequestType {
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,
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(),
@ -85,67 +92,104 @@ impl Mediawiki {
api_user: api_user.to_string(),
api_secret: api_secret.to_string(),
is_dry_run,
login_token: String::new(),
csrf_token: String::new(),
login_token: OnceCell::new(),
csrf_token: OnceCell::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"),
pub fn login (&self) -> Result<(), Box<dyn Error>> {
let url = format!("{}/api.php?", self.server_url);
// retrieve login token first
let params_0: Box<[(&str, &str)]> = Box::from([
("action", "query"),
("meta", "tokens"),
("type", "login"),
("action", "query")
("format", "json")
]);
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")
verboseln!("Login params: {:?}", params_0);
let resp_0: String = self.make_request(url.clone(), params_0, ValidRequestTypes::Get)?;
verboseln!("Raw response login_0: {}", resp_0.yellow());
let resp_0_deserialized: serde_json::Value = serde_json::from_str(&resp_0)?;
verboseln!("login0 deserialized");
let login_token = resp_0_deserialized
.pointer("/query/tokens/logintoken")
.and_then(|v| v.as_str())
.map(|token| token.replace("+\\", ""))
.ok_or("Login token not found")?;
self.login_token.set(login_token)?;
verboseln!("login0 finished");
let login_token: String = self.login_token.clone().into_inner().unwrap();
verboseln!("using {login_token} as login");
let params_1: Box<[(&str, &str)]> = Box::from([
("action", "login"),
("lgname", &self.api_user),
("lgpassword", &self.api_secret),
("lgtoken", &login_token),
("format", "json")
]);
let resp: Result<String, Box<dyn Error>> = self.make_request(url, params, RequestType::Post);
Ok(resp.unwrap())
verboseln!("Login params: {:?}", params_1);
let resp_1: String = self.client
.post(&url)
// .basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
.form(&params_1)
.send()?
.text()?;
verboseln!("Raw response login_1: {}", resp_1.yellow());
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 result.as_str() == Some("Success") {
Ok(())
} else {
Err(format!("Login failed. Response: {}", resp_1_deserialized).into())
}
} else {
Err(format!("Unexpected login response: {}", resp_1_deserialized).into())
}
}
pub fn get_csrf_token(&self) -> Result<String, Box<dyn Error>> { // HAS TO BE FIXED
pub fn get_csrf_token(&self) -> Result<(), Box<dyn Error>> {
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 resp: String = self.make_request(url, params, ValidRequestTypes::Get)?;
verboseln!("Raw response csrf: {}", resp);
let response_deserialized: QueryResponseCsrf = serde_json::from_str(&resp)?;
Ok(response_deserialized.query.tokens.csrftoken)
let token = response_deserialized.query.tokens.csrftoken;
verboseln!("Parsed token: '{}'", token);
if token == "+\\" {
return Err("Failed to parse CSRF token. Response was only '+\\'. Please check your Bot API credentials".into());
}
self.csrf_token.set(token)?;
verboseln!("CSRF token acquired: {}", self.csrf_token.get().unwrap());
Ok(())
}
pub fn make_request(&self, url: String, params: Box<[(&str, &str)]>, request_type: RequestType) -> Result<String, Box<dyn Error>> {
pub fn make_request(&self, url: String, params: Box<[(&str, &str)]>, request_type: ValidRequestTypes) -> Result<String, Box<dyn Error>> {
let resp: Result<String, Box<dyn Error>> = match
match request_type {
RequestType::Get => {
ValidRequestTypes::Get => {
self
.client
.get(url)
//.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
.query(&params)
.send()
}
RequestType::Post | RequestType::PostForEditing => {
ValidRequestTypes::Post | ValidRequestTypes::PostForEditing => {
// convert the params into a HashMap for JSON
let params_map: std::collections::HashMap<_, _> = params.iter().cloned().collect();
self
.client
.post(url)
//.basic_auth(&self.http_user, Some(&self.http_password)) ZU TESTZWECKEN ENTFERNT
.form(&params)
//.basic_auth(&self.http_user, Some(&self.http_password)) // TODO: ZU TESTZWECKEN ENTFERNT
.query(&params_map)
.send()
}
}
@ -153,8 +197,8 @@ impl Mediawiki {
Ok(response) => {
if response.status().is_success() {
match request_type {
RequestType::PostForEditing => Ok(response.text().unwrap()),
_ => Ok(response.text().unwrap())
ValidRequestTypes::PostForEditing => Ok(response.text()?),
_ => Ok(response.text()?)
}
}
else {
@ -171,25 +215,267 @@ impl Mediawiki {
};
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);
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"), // Create and edit pages.
("action", "edit"),
("format", "json"),
("title", page_title), // Title of the page to edit. Cannot be used together with pageid.
("appendtext", page_content), // Add this text to the end of the page or section. Overrides text.
("token", self.csrf_token.as_str()), // A "csrf" token retrieved from action=query&meta=tokens
("bot", "true"), // Mark this edit as a bot edit.
("title", page_title),
("text", page_content),
("token", self.csrf_token.get().unwrap()),
("createonly", "true"), // Prevent overwriting existing pages
("bot", "true"),
]);
self.make_request(url, params, RequestType::Post)
// 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())
}
}
/// 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>> {
let current_year = Utc::now().year().to_string();
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 {
// 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)
);
}
// 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);
let params: Box<[(&str, &str)]> = Box::from([
("action", "parse"),
("prop", "wikitext"),
("format", "json"),
("page", page_title),
("formatversion", "2"),
]);
let resp = self.make_request(url, params, ValidRequestTypes::Get)?;
let response_deserialized: serde_json::Value = serde_json::from_str(&resp)?;
let wikitext = response_deserialized
.get("parse")
.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
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(&params)
.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, 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
verboseln!("logging in...");
let login_result = wiki.login()?;
verboseln!("Login done.");
trace_var!(login_result);
wiki.get_csrf_token()?;
verboseln!("CSRF token acquired.");
// Convert to mediawiki and make new page
let pad_converted = pandoc_convert(old_pad_content)?;
trace_var!(pad_converted);
// Create a new wiki page plenum_main_page_name/page_title, e.g. under Plenum/13._August_2024
verboseln!("wiki: uploading converted pad");
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)?;
verboseln!("Finished successfully with wiki");
Ok(())
}
/// Takes a String in the Markdown format, converts it and returns it in
/// the Mediawiki format
fn pandoc_convert(markdown: String) -> Result<String, Box<dyn Error>> {
let (output, errors, status) = crate::pipe(
"pandoc",
&mut ["--from", "markdown-auto_identifiers", "--to", "mediawiki", "--no-highlight"],
markdown,
)?;
if status.success() {
println!("Resultat von Pandoc: {}", output);
Ok(output)
} else {
Err(format!("Pandoc error, exit code {:?}\n{}", status, errors).into())
}
}
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
#[derive(Deserialize)]
struct QueryResponseLogin {
#[allow(dead_code)]
batchcomplete: String,
query: QueryTokensLogin,
}
@ -206,7 +492,7 @@ struct TokensLogin {
#[derive(Deserialize)]
struct QueryResponseCsrf {
batchcomplete: bool,
#[allow(dead_code)]
query: crate::mediawiki::QueryTokensCsrf,
}
@ -219,75 +505,3 @@ struct QueryTokensCsrf {
struct TokensCsrf {
csrftoken: String,
}
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);
//wiki.new_wiki_page("Page Test 5", &pad_converted)?; JUST AN EXAMPLE
// 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
}