251 lines
7.3 KiB
Rust
251 lines
7.3 KiB
Rust
use crate::util;
|
|
|
|
use ansi_term::{self, Colour};
|
|
use clap::Parser;
|
|
use directories::{BaseDirs, UserDirs};
|
|
use eyre::Result;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::thread;
|
|
use std::{
|
|
io::Read,
|
|
path::{Path, PathBuf},
|
|
time::Duration,
|
|
};
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct HistoryItem {
|
|
#[serde(rename = "FileName")]
|
|
pub file_name: String,
|
|
#[serde(rename = "FilePath")]
|
|
pub file_path: Option<String>,
|
|
#[serde(rename = "DateTime")]
|
|
pub date_time: String,
|
|
#[serde(rename = "Type")]
|
|
pub type_field: String,
|
|
#[serde(rename = "Host")]
|
|
pub host: String,
|
|
#[serde(rename = "Tags")]
|
|
pub tags: Option<Tags>,
|
|
#[serde(rename = "URL")]
|
|
pub url: Option<String>,
|
|
#[serde(rename = "ThumbnailURL")]
|
|
pub thumbnail_url: Option<String>,
|
|
#[serde(rename = "DeletionURL")]
|
|
pub deletion_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct Tags {
|
|
#[serde(rename = "WindowTitle")]
|
|
pub window_title: Option<String>,
|
|
#[serde(rename = "ProcessName")]
|
|
pub process_name: String,
|
|
}
|
|
|
|
pub async fn handler(pathflag: Option<PathBuf>) -> Result<()> {
|
|
// TODO
|
|
// If a pathflag is Some() it means it's passed via a flag, use that path and skip the prompting
|
|
// Otherwise, prompt the user to select a default path (depending on type of installation), or tinyfiledialog for a path, or manual input
|
|
|
|
let path = match pathflag {
|
|
Some(pathflag) => pathflag,
|
|
None => get_path_input(),
|
|
};
|
|
|
|
let file = prompt_history_file(&path);
|
|
|
|
if !Path::new(&file).exists() {
|
|
eprintln!(
|
|
"{} The directory does not exist. Given path: {:?}",
|
|
Colour::Red.paint("Error:"),
|
|
&file
|
|
);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let history_urls = get_history_urls(&file);
|
|
|
|
delete_urls(history_urls).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn prompt_history_file(path: &Path) -> PathBuf {
|
|
// TODO: Use dialoguer select to prompt the user to select the history file, either with tinyfiledialogs or manual input
|
|
|
|
return tinyfiledialogs::open_file_dialog(
|
|
"Choose where sharex history is stored",
|
|
path.to_str().unwrap(),
|
|
Some((&["History.json", "*.json"], "History.json")),
|
|
)
|
|
.map_or_else(
|
|
|| {
|
|
eprintln!("No file selected, exiting...");
|
|
std::process::exit(1);
|
|
},
|
|
PathBuf::from,
|
|
);
|
|
}
|
|
|
|
fn get_history_urls(path: &PathBuf) -> Result<Vec<String>> {
|
|
let spinner = util::setup_spinner("Reading and parsing JSON...");
|
|
|
|
let history_json = read_history_json(&path)?;
|
|
let history_items = parse_history_json(&history_json)?;
|
|
let deletion_urls = get_deletion_urls(&history_items);
|
|
|
|
// spinner.finish_and_clear();
|
|
spinner.finish_with_message(format!("Done! {} items found", deletion_urls.len()));
|
|
Ok(deletion_urls)
|
|
}
|
|
|
|
fn get_path_input() -> PathBuf {
|
|
let args = util::Args::parse();
|
|
let path = match args.command {
|
|
Some(util::Command::MassDelete { path }) => path,
|
|
None => None,
|
|
};
|
|
|
|
let default_history_path = get_default_history_path();
|
|
|
|
match path {
|
|
Some(path) => path,
|
|
None => default_history_path.clone(),
|
|
}
|
|
}
|
|
|
|
fn get_default_history_path() -> PathBuf {
|
|
let document_directory: PathBuf = if let Some(user_dirs) = UserDirs::new() {
|
|
user_dirs.document_dir().unwrap().to_path_buf()
|
|
} else {
|
|
BaseDirs::new().unwrap().home_dir().join("Documents")
|
|
};
|
|
let default_history_path: PathBuf = document_directory.join("ShareX").join("History.json");
|
|
|
|
default_history_path
|
|
}
|
|
|
|
fn read_history_json(path: &PathBuf) -> Result<String> {
|
|
let mut file = std::fs::File::open(path)?;
|
|
let mut contents = String::new();
|
|
file.read_to_string(&mut contents)?;
|
|
|
|
// Since ShareX history is invalid JSON we add brackets to make it valid JSON
|
|
contents = format!("[{}]", contents);
|
|
Ok(contents)
|
|
}
|
|
|
|
fn parse_history_json(json: &str) -> Result<Vec<HistoryItem>, serde_json::Error> {
|
|
let history_items: Vec<HistoryItem> = serde_json::from_str(json)?;
|
|
Ok(history_items)
|
|
}
|
|
|
|
fn get_deletion_urls(items: &[HistoryItem]) -> Vec<String> {
|
|
items
|
|
.iter()
|
|
.filter(|item| item.deletion_url.is_some() && item.deletion_url != Some("".to_string()))
|
|
.map(|item| item.deletion_url.clone().unwrap())
|
|
.collect()
|
|
}
|
|
|
|
async fn delete_urls(deletion_urls: Result<Vec<String>>) -> Result<()> {
|
|
let deletion_urls = deletion_urls?;
|
|
let progress_bar = util::setup_progressbar(deletion_urls.len());
|
|
// progress_bar.enable_steady_tick(Duration::from_millis(500)); // This only visually updates the ticker once every 500ms instead of when the tick occurs
|
|
|
|
// ? Maybe use Rayon to parallelize the requests and run them through public proxies to prevent rate limiting?
|
|
for url in deletion_urls {
|
|
let (remaining, limit, reset) = send_deletion(&url).await?;
|
|
println!(
|
|
"Remaining: {} Limit: {} Reset: {}",
|
|
Colour::Green.paint(remaining),
|
|
Colour::Yellow.paint(limit),
|
|
Colour::Red.paint(reset)
|
|
);
|
|
progress_bar.inc(1);
|
|
thread::sleep(Duration::from_millis(100));
|
|
}
|
|
// let client = reqwest::Client::new();
|
|
|
|
// let mut futures = Vec::new();
|
|
|
|
// for url in deletion_urls {
|
|
// let future = client.delete(&url).send();
|
|
// futures.push(future);
|
|
// }
|
|
|
|
// let results = futures::future::join_all(futures).await;
|
|
|
|
// for result in results {
|
|
// match result {
|
|
// Ok(response) => {
|
|
// if response.status().is_success() {
|
|
// println!("Deleted {}", response.url());
|
|
// } else {
|
|
// eprintln!("Failed to delete {}", response.url());
|
|
// }
|
|
// }
|
|
// Err(e) => {
|
|
// eprintln!("Failed to delete {}", e);
|
|
// }
|
|
// }
|
|
// }
|
|
progress_bar.finish_with_message("Done!");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn send_deletion(url: &str) -> Result<(String, String, String)> {
|
|
let client = reqwest::Client::new();
|
|
let params = [("confirm", true)];
|
|
let resp = client.post(url).form(¶ms).send().await?;
|
|
|
|
println!("{:#?}", resp);
|
|
|
|
match resp.status() {
|
|
reqwest::StatusCode::OK => {
|
|
println!("OK");
|
|
}
|
|
reqwest::StatusCode::TOO_MANY_REQUESTS => {
|
|
println!("TOO MANY REQUESTS");
|
|
}
|
|
reqwest::StatusCode::BAD_GATEWAY => {
|
|
println!("BAD GATEWAY");
|
|
}
|
|
_ => {
|
|
println!("Not OK");
|
|
}
|
|
}
|
|
|
|
// I don't understand Rust enough so the stuff below looks kinda cursed
|
|
let headers = resp.headers().clone();
|
|
let remaining = headers
|
|
.get("x-post-rate-limit-remaining")
|
|
.unwrap()
|
|
.to_str()?
|
|
.to_owned();
|
|
let limit = headers
|
|
.get("x-post-rate-limit-limit")
|
|
.unwrap()
|
|
.to_str()?
|
|
.to_owned();
|
|
let reset = headers
|
|
.get("x-post-rate-limit-Reset")
|
|
.unwrap()
|
|
.to_str()?
|
|
.to_owned();
|
|
|
|
println!(
|
|
"Remaining: {} Limit: {} Reset: {}",
|
|
Colour::Green.paint(&remaining),
|
|
Colour::Yellow.paint(&limit),
|
|
Colour::Red.paint(&reset)
|
|
);
|
|
|
|
print!("{:?}", headers);
|
|
|
|
Ok((remaining, limit, reset))
|
|
}
|