diff --git a/src/main.rs b/src/main.rs index 734586b..5e46d89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,129 +1,28 @@ -use reqwest::{blocking::Client, blocking::ClientBuilder, header::HeaderValue, redirect::Policy}; -use scraper::{Html, Selector}; +use crate::rentry::{build_client, delete_paste, CSRFData}; -const URL: &str = "https://rentry.co/Hyprland-controversy/edit"; +mod rentry; -enum DeleteResult { - 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 { - 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::(); - 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::(); - let err_txt = err_txt.trim().to_string(); - - DeleteResult::RequestFailure(err_txt) -} +const PASTE_NAME: &str = "Hyprland-controversy"; fn main() { - let client = ClientBuilder::new() - .redirect(Policy::none()) - .build() - .unwrap(); - let csrf_data = get_csrf_data(&client); - println!("Token: {csrf_data:?}"); + let client = build_client(); + let csrf_data = CSRFData::get(&client, PASTE_NAME); - match attempt_delete(&client, &csrf_data, "qwack") { - DeleteResult::Success => println!("Successfully deleted!"), - DeleteResult::InvalidEditCode => println!("Failed to delete: Invalid edit code!"), - DeleteResult::RequestFailure(txt) => println!("Failed to delete (req): {txt}!"), - DeleteResult::UnknownFailure(txt) => println!("Failed to delete (unknown): {txt}!"), + let edit_code = "f"; + match delete_paste(&client, &csrf_data, edit_code, PASTE_NAME) { + Ok(_) => println!("Paste deleted successfully"), + Err(e) => match e { + 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:?}"), + }, } } diff --git a/src/rentry.rs b/src/rentry.rs new file mode 100644 index 0000000..b73d6f7 --- /dev/null +++ b/src/rentry.rs @@ -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 { + 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 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 for DeleteError { + fn from(err: reqwest::Error) -> Self { + DeleteError::ReqwestError(err) + } +} + +impl From 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::(); + 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::(); + 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() +}