Move rentry logic to separate file
This commit is contained in:
parent
b76274e361
commit
e577f01fe1
143
src/main.rs
143
src/main.rs
|
@ -1,129 +1,28 @@
|
||||||
use reqwest::{blocking::Client, blocking::ClientBuilder, header::HeaderValue, redirect::Policy};
|
use crate::rentry::{build_client, delete_paste, CSRFData};
|
||||||
use scraper::{Html, Selector};
|
|
||||||
|
|
||||||
const URL: &str = "https://rentry.co/Hyprland-controversy/edit";
|
mod rentry;
|
||||||
|
|
||||||
enum DeleteResult {
|
const PASTE_NAME: &str = "Hyprland-controversy";
|
||||||
Success,
|
|
||||||
InvalidEditCode,
|
|
||||||
UnknownFailure(String),
|
|
||||||
RequestFailure(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CSRFData {
|
|
||||||
cookie: String,
|
|
||||||
form: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cookie_value(header_value: &HeaderValue, cookie_name: &str) -> Option<String> {
|
|
||||||
let cookies_str = header_value.to_str().ok()?;
|
|
||||||
cookies_str.split(';').find_map(|cookie| {
|
|
||||||
let mut parts = cookie.split('=');
|
|
||||||
let name = parts.next()?.trim();
|
|
||||||
let value = parts.next()?.trim();
|
|
||||||
if name == cookie_name {
|
|
||||||
Some(value.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_csrf_data(client: &Client) -> CSRFData {
|
|
||||||
let res = client.get(URL).send().unwrap();
|
|
||||||
let cookies = res.headers().get("Set-Cookie").unwrap();
|
|
||||||
let cookie_middleware_token = get_cookie_value(cookies, "csrftoken").unwrap();
|
|
||||||
|
|
||||||
let body = res.text().unwrap();
|
|
||||||
let document = Html::parse_document(&body);
|
|
||||||
let selector = Selector::parse("input[name=\"csrfmiddlewaretoken\"]").unwrap();
|
|
||||||
let form_middleware_token = document
|
|
||||||
.select(&selector)
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.value()
|
|
||||||
.attr("value")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
CSRFData {
|
|
||||||
cookie: cookie_middleware_token,
|
|
||||||
form: form_middleware_token.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn attempt_delete(client: &Client, csrf_data: &CSRFData, edit_code: &str) -> DeleteResult {
|
|
||||||
let res = client
|
|
||||||
.post(URL)
|
|
||||||
.header("Referer", URL)
|
|
||||||
.header(
|
|
||||||
"Cookie",
|
|
||||||
"csrftoken=".to_string() + csrf_data.cookie.as_str(),
|
|
||||||
)
|
|
||||||
.form(&[
|
|
||||||
("csrfmiddlewaretoken", csrf_data.form.as_str()),
|
|
||||||
("edit_code", edit_code),
|
|
||||||
("new_edit_code", ""),
|
|
||||||
("new_url", ""),
|
|
||||||
("text", ""),
|
|
||||||
("delete", "delete"),
|
|
||||||
])
|
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Get these before obtaining text, as that consumes the response
|
|
||||||
let status = res.status();
|
|
||||||
let headers = res.headers().clone();
|
|
||||||
|
|
||||||
let raw_txt = res.text().unwrap();
|
|
||||||
let document = Html::parse_document(&raw_txt);
|
|
||||||
|
|
||||||
if status.is_redirection() {
|
|
||||||
let location = headers.get("Location").unwrap().to_str().unwrap();
|
|
||||||
if location == "/" {
|
|
||||||
return DeleteResult::Success;
|
|
||||||
}
|
|
||||||
return DeleteResult::RequestFailure("unexpected redirection: ".to_string() + location);
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.is_success() {
|
|
||||||
let selector =
|
|
||||||
Selector::parse("fieldset > div.text-danger.messages > ul.errorlist > li").unwrap();
|
|
||||||
if let Some(element) = document.select(&selector).next() {
|
|
||||||
let txt = element.text().collect::<String>();
|
|
||||||
if txt == "Invalid edit code." {
|
|
||||||
return DeleteResult::InvalidEditCode;
|
|
||||||
}
|
|
||||||
return DeleteResult::UnknownFailure("form error: ".to_string() + &txt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DeleteResult::UnknownFailure(format!("unknown failure: {raw_txt}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let selector = Selector::parse("span").unwrap();
|
|
||||||
let err_txt = document
|
|
||||||
.select(&selector)
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.collect::<String>();
|
|
||||||
let err_txt = err_txt.trim().to_string();
|
|
||||||
|
|
||||||
DeleteResult::RequestFailure(err_txt)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let client = ClientBuilder::new()
|
let client = build_client();
|
||||||
.redirect(Policy::none())
|
let csrf_data = CSRFData::get(&client, PASTE_NAME);
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let csrf_data = get_csrf_data(&client);
|
|
||||||
println!("Token: {csrf_data:?}");
|
|
||||||
|
|
||||||
match attempt_delete(&client, &csrf_data, "qwack") {
|
let edit_code = "f";
|
||||||
DeleteResult::Success => println!("Successfully deleted!"),
|
match delete_paste(&client, &csrf_data, edit_code, PASTE_NAME) {
|
||||||
DeleteResult::InvalidEditCode => println!("Failed to delete: Invalid edit code!"),
|
Ok(_) => println!("Paste deleted successfully"),
|
||||||
DeleteResult::RequestFailure(txt) => println!("Failed to delete (req): {txt}!"),
|
Err(e) => match e {
|
||||||
DeleteResult::UnknownFailure(txt) => println!("Failed to delete (unknown): {txt}!"),
|
rentry::DeleteError::RequestFailure(msg) => eprintln!("Request error (non 2XX): {msg}"),
|
||||||
|
rentry::DeleteError::UnexpectedRedirect(msg) => eprintln!("Unexpected redirect: {msg}"),
|
||||||
|
rentry::DeleteError::FormError(msg) => {
|
||||||
|
if msg == "Invalid edit code." {
|
||||||
|
eprintln!("Invalid edit code")
|
||||||
|
} else {
|
||||||
|
eprintln!("Form error: {msg}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rentry::DeleteError::UnknownFailure(msg) => eprintln!("Unknown failure: {msg}"),
|
||||||
|
rentry::DeleteError::ReqwestError(e) => eprintln!("Request failed with: {e:?}"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
160
src/rentry.rs
Normal file
160
src/rentry.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use reqwest::{
|
||||||
|
blocking::Client,
|
||||||
|
blocking::{ClientBuilder, Response},
|
||||||
|
header::HeaderValue,
|
||||||
|
redirect::Policy,
|
||||||
|
};
|
||||||
|
use scraper::{Html, Selector};
|
||||||
|
|
||||||
|
const BASE_URL: &str = "https://rentry.co/";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CSRFData {
|
||||||
|
cookie: String,
|
||||||
|
form: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cookie_value(header_value: &HeaderValue, cookie_name: &str) -> Option<String> {
|
||||||
|
let cookies_str = header_value.to_str().ok()?;
|
||||||
|
cookies_str.split(';').find_map(|cookie| {
|
||||||
|
let mut parts = cookie.split('=');
|
||||||
|
let name = parts.next()?.trim();
|
||||||
|
let value = parts.next()?.trim();
|
||||||
|
if name == cookie_name {
|
||||||
|
Some(value.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Response> for CSRFData {
|
||||||
|
fn from(res: Response) -> Self {
|
||||||
|
let cookies = res.headers().get("Set-Cookie").unwrap();
|
||||||
|
let cookie_middleware_token = get_cookie_value(cookies, "csrftoken").unwrap();
|
||||||
|
|
||||||
|
let body = res.text().unwrap();
|
||||||
|
let document = Html::parse_document(&body);
|
||||||
|
let selector = Selector::parse("input[name=\"csrfmiddlewaretoken\"]").unwrap();
|
||||||
|
let form_middleware_token = document
|
||||||
|
.select(&selector)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.value()
|
||||||
|
.attr("value")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
CSRFData {
|
||||||
|
cookie: cookie_middleware_token,
|
||||||
|
form: form_middleware_token.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSRFData {
|
||||||
|
pub fn get(client: &Client, paste_name: &str) -> Self {
|
||||||
|
let url = BASE_URL.to_string() + paste_name + "/edit";
|
||||||
|
let res = client.get(url).send().unwrap();
|
||||||
|
CSRFData::from(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DeleteError {
|
||||||
|
ReqwestError(reqwest::Error),
|
||||||
|
UnexpectedRedirect(String),
|
||||||
|
FormError(String),
|
||||||
|
RequestFailure(String),
|
||||||
|
UnknownFailure(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for DeleteError {
|
||||||
|
fn from(err: reqwest::Error) -> Self {
|
||||||
|
DeleteError::ReqwestError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Response> for DeleteError {
|
||||||
|
fn from(res: Response) -> Self {
|
||||||
|
let status = res.status();
|
||||||
|
let headers = res.headers().clone();
|
||||||
|
|
||||||
|
let raw_txt = res.text().unwrap();
|
||||||
|
let document = Html::parse_document(&raw_txt);
|
||||||
|
|
||||||
|
if status.is_redirection() {
|
||||||
|
let location = headers.get("Location").unwrap().to_str().unwrap();
|
||||||
|
if location == "/" {
|
||||||
|
// This is supposed to be handled from delete_paste function,
|
||||||
|
// This is an error enum, we can't represent success
|
||||||
|
panic!("response is successfult");
|
||||||
|
}
|
||||||
|
return Self::UnexpectedRedirect(location.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2XX
|
||||||
|
if status.is_success() {
|
||||||
|
let selector =
|
||||||
|
Selector::parse("fieldset > div.text-danger.messages > ul.errorlist > li").unwrap();
|
||||||
|
if let Some(element) = document.select(&selector).next() {
|
||||||
|
let txt = element.text().collect::<String>();
|
||||||
|
return DeleteError::FormError(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeleteError::UnknownFailure(format!("unknown failure (2XX): {raw_txt}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let selector = Selector::parse("span").unwrap();
|
||||||
|
let err_txt = document
|
||||||
|
.select(&selector)
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.collect::<String>();
|
||||||
|
let err_txt = err_txt.trim().to_string();
|
||||||
|
|
||||||
|
DeleteError::RequestFailure(err_txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_paste(
|
||||||
|
client: &Client,
|
||||||
|
csrf_data: &CSRFData,
|
||||||
|
edit_code: &str,
|
||||||
|
paste_name: &str,
|
||||||
|
) -> Result<(), DeleteError> {
|
||||||
|
let url = BASE_URL.to_string() + paste_name + "/edit";
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(url.clone())
|
||||||
|
.header("Referer", url)
|
||||||
|
.header(
|
||||||
|
"Cookie",
|
||||||
|
"csrftoken=".to_string() + csrf_data.cookie.as_str(),
|
||||||
|
)
|
||||||
|
.form(&[
|
||||||
|
("csrfmiddlewaretoken", csrf_data.form.as_str()),
|
||||||
|
("edit_code", edit_code),
|
||||||
|
("new_edit_code", ""),
|
||||||
|
("new_url", ""),
|
||||||
|
("text", ""),
|
||||||
|
("delete", "delete"),
|
||||||
|
])
|
||||||
|
.send()?;
|
||||||
|
|
||||||
|
if res.status().is_redirection() {
|
||||||
|
let location = res.headers().get("Location").unwrap().to_str().unwrap();
|
||||||
|
if location == "/" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(DeleteError::from(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_client() -> Client {
|
||||||
|
ClientBuilder::new()
|
||||||
|
.redirect(Policy::none())
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
Loading…
Reference in a new issue