diff --git a/Cargo.lock b/Cargo.lock index 9207105..abdaccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -517,12 +517,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "http" version = "1.5.0" @@ -894,12 +888,10 @@ dependencies = [ "colored", "dialoguer", "futures-util", - "hex", "indicatif", "reqwest", "serde", "sha1", - "sha2", "terminal_size", "textwrap", "tokio", @@ -1420,17 +1412,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shell-words" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index da5dac3..e5df991 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,10 @@ clap = { version = "4.6.6", features = ["derive"] } colored = "3.1.1" dialoguer = { version = "0.12.0", features = ["fuzzy-select"] } futures-util = "0.3.34" -hex = "0.4.3" indicatif = "0.18.6" reqwest = { version = "0.13.4", features = ["json", "stream", "query"] } serde = { version = "1.0.229", features = ["derive"] } sha1 = "0.11.0" -sha2 = "0.11.0" terminal_size = "0.4.4" textwrap = "0.16.2" tokio = { version = "1.53.1", features = ["full"] } diff --git a/src/commands/create.rs b/src/commands/create.rs index bd27aea..91a0a85 100644 --- a/src/commands/create.rs +++ b/src/commands/create.rs @@ -2,6 +2,7 @@ use std::error::Error; use dialoguer::{FuzzySelect, Select, theme::ColorfulTheme}; use futures_util::StreamExt; +use sha1::{Digest, Sha1}; use tokio::{fs::File, io::AsyncWriteExt}; use crate::{ @@ -10,7 +11,7 @@ use crate::{ fabric::FabricVersions, vanilla::{VanillaVersionDetail, VanillaVersionType, VanillaVersions}, }, - utils::{verify_sha1, with_progressbar, with_spinner}, + utils::{with_progressbar, with_spinner}, }; pub struct Create { @@ -171,10 +172,12 @@ impl LoaderDownloader for VanillaDownloader { with_spinner("verifying sha1 hash", "sha1 hash verified", || async { let data = tokio::fs::read("server.jar").await?; + let mut hasher = Sha1::new(); + hasher.update(&data); + let hash = hasher.finalize(); + let hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect(); - let verified = verify_sha1(&data, &version_detail.downloads.server.sha1); - - if !verified { + if hex != version_detail.downloads.server.sha1 { tokio::fs::remove_file("server.jar").await?; return Err("sha1 verifaction failed for downloaded file".into()); } @@ -294,8 +297,6 @@ impl LoaderDownloader for FabricDownloader { }) .await?; - tokio::fs::create_dir("mods").await?; - let config = Config { server: Server { version: create.version.clone().unwrap(), diff --git a/src/commands/install.rs b/src/commands/install.rs deleted file mode 100644 index 45b7579..0000000 --- a/src/commands/install.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::error::Error; - -use futures_util::StreamExt; -use tokio::io::AsyncWriteExt; - -use crate::{ - models::{ - config::Config, - install::{InstallQueryParams, InstallResult}, - }, - utils::{verify_sha512, with_progressbar, with_spinner}, -}; - -pub struct Install { - mods: Vec, -} - -impl Install { - const BASE_URL: &str = "https://api.modrinth.com/v2"; - - pub fn new(mods: Vec) -> Self { - Install { mods } - } - - pub async fn run(&self) -> Result<(), Box> { - let file_res = tokio::fs::read_to_string("mcsm.toml").await; - if file_res.is_err() { - return Err("current directory is not a mcsm project".into()); - } - let config: Config = toml::from_str(&file_res.unwrap())?; - - if config.server.loader == "Vanilla" { - return Err("cannot search for mods on Vanilla server".into()); - } - - if std::env::set_current_dir("mods").is_err() { - return Err("mods folder not found".into()); - } - - let client = reqwest::Client::new(); - - for mod_name in &self.mods { - let url = format!("{}/project/{}/version", Self::BASE_URL, mod_name); - - let params = InstallQueryParams { - loaders: format!(r#"["{}"]"#, config.server.loader).to_lowercase(), - game_versions: format!(r#"["{}"]"#, config.server.version), - }; - - let install_result: Vec = with_spinner( - format!("fetching {}", mod_name).as_str(), - format!("fetched {}", mod_name).as_str(), - || async { - let res = client.get(url).query(¶ms).send().await; - - if res.is_err() { - return Err(format!(r#"mod "{}" not found"#, mod_name).into()); - } - - Ok(res.unwrap().json().await?) - }, - ) - .await?; - - let file = install_result.first().unwrap().files.first().unwrap(); - with_progressbar( - format!("downloading {}", mod_name).as_str(), - format!("downloaded {}", mod_name).as_str(), - file.size, - |pb| async move { - let mod_bytes = reqwest::get(&file.url).await?; - let mut file = tokio::fs::File::create(&file.filename).await?; - let mut stream = mod_bytes.bytes_stream(); - - while let Some(chunk) = stream.next().await { - let chunk = chunk?; - file.write_all(&chunk).await?; - pb.inc(chunk.len() as u64); - } - - Ok(()) - }, - ) - .await?; - - with_spinner("verifying sha512 hash", "sha512 hash verified", || async { - let data = tokio::fs::read(&file.filename).await?; - let verified = verify_sha512(&data, &file.hashes.sha512); - - if !verified { - tokio::fs::remove_file("server.jar").await?; - return Err("sha1 verifaction failed for downloaded file".into()); - } - - Ok(()) - }) - .await?; - println!(); - } - - println!( - "{} finished installing mods", - dialoguer::console::style("✔").green() - ); - - Ok(()) - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 83e620d..eede39f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,2 @@ pub mod create; -pub mod install; pub mod search; diff --git a/src/commands/search.rs b/src/commands/search.rs index 2b5b335..aa00fe0 100644 --- a/src/commands/search.rs +++ b/src/commands/search.rs @@ -1,14 +1,20 @@ use std::error::Error; use colored::Colorize; +use serde::Serialize; use terminal_size; -use terminal_size::{Width, terminal_size as get_terminal_size}; use textwrap::{Options, fill}; +use terminal_size::{terminal_size as get_terminal_size, Width}; -use crate::models::{ - config::Config, - search::{SearchQueryParams, SearchResult}, -}; +use crate::models::{config::Config, search::SearchResult}; + +#[derive(Serialize)] +struct SearchQueryParams { + query: String, + facets: String, + offset: Option, + limit: Option, +} pub struct Search { query: String, @@ -19,12 +25,12 @@ pub struct Search { impl Search { const BASE_URL: &str = "https://api.modrinth.com/v2"; - pub fn new(query: String, offset: Option, limit: Option) -> Self { - Search { - query, - offset, - limit, - } + pub fn new( + query: String, + offset: Option, + limit: Option, + ) -> Self { + Search { query, offset, limit } } pub async fn run(&self) -> Result<(), Box> { @@ -60,7 +66,7 @@ impl Search { .await? .json() .await?; - + self.print_results(&res); Ok(()) @@ -69,18 +75,12 @@ impl Search { fn print_results(&self, res: &SearchResult) { println!( "{}\n", - format!( - "Found {} result{}", - res.total_hits, - if res.total_hits == 1 { "" } else { "s" } - ) - .green() - .bold() + format!("Found {} result{}", res.total_hits, if res.total_hits == 1 { "" } else { "s" }) + .green() + .bold() ); - let width = get_terminal_size() - .map(|(Width(w), _)| w as usize) - .unwrap_or(80); + let width = get_terminal_size().map(|(Width(w), _)| w as usize).unwrap_or(80); for (i, hit) in res.hits.iter().enumerate() { let wrapped = fill( diff --git a/src/main.rs b/src/main.rs index 6d33756..c503db0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ mod commands; mod models; mod utils; -use crate::commands::{create::Create, install::Install, search::Search}; +use crate::commands::{create::Create, search::Search}; use clap::{ Parser, Subcommand, builder::styling::{self}, @@ -53,13 +53,13 @@ enum Commands { #[arg(long)] show_alphas: bool, }, - + /// Search for mods on modrinth for project in current directory /// automticallly filters mod loader, minecraft version from config file Search { /// Search term to look up, e.g. project name or keyword. query: String, - + /// Number of results to skip before returning matches. #[arg(long)] offset: Option, @@ -68,13 +68,6 @@ enum Commands { #[arg(long, value_parser = clap::value_parser!(u32).range(1..=100))] limit: Option, }, - - /// Install mods from modrinth for project in current directory - Install { - /// List of mods to install - #[arg(trailing_var_arg = true)] - mods: Vec, - }, } #[tokio::main] @@ -108,18 +101,10 @@ async fn main() -> Result<(), Box> { let config = create.download_server().await?; create.save_config(config).await?; } - Commands::Search { - query, - offset, - limit, - } => { + Commands::Search { query, offset, limit } => { let search = Search::new(query, offset, limit); search.run().await?; } - Commands::Install { mods } => { - let install = Install::new(mods); - install.run().await?; - } } Ok(()) diff --git a/src/models/install.rs b/src/models/install.rs deleted file mode 100644 index 145f29c..0000000 --- a/src/models/install.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize)] -pub struct InstallQueryParams { - pub loaders: String, - pub game_versions: String, -} - -#[derive(Deserialize)] -pub struct InstallResult { - pub version_number: String, - pub files: Vec, -} - -#[derive(Deserialize)] -pub struct InstallFile { - pub hashes: InstallHashes, - pub url: String, - pub filename: String, - pub size: u64, -} - -#[derive(Deserialize)] -pub struct InstallHashes { - pub sha512: String, -} diff --git a/src/models/mod.rs b/src/models/mod.rs index f6747d3..6a75d69 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,4 @@ pub mod config; pub mod fabric; -pub mod install; pub mod search; pub mod vanilla; diff --git a/src/models/search.rs b/src/models/search.rs index 11929d5..2be2b5e 100644 --- a/src/models/search.rs +++ b/src/models/search.rs @@ -1,12 +1,4 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize)] -pub struct SearchQueryParams { - pub query: String, - pub facets: String, - pub offset: Option, - pub limit: Option, -} +use serde::Deserialize; #[derive(Deserialize)] pub struct SearchResult { diff --git a/src/utils.rs b/src/utils.rs index 61b5a92..9b539ce 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,4 @@ use indicatif::{ProgressBar, ProgressStyle}; -use sha1::{Digest, Sha1}; -use sha2::Sha512; use std::{error::Error, future::Future}; pub async fn with_spinner( @@ -44,20 +42,7 @@ where pb.set_message(start_msg.to_string()); let result = f(pb.clone()).await; - let formatted_finish_msg = format!("{} {}", dialoguer::console::style("✔").green(), finish_msg); - pb.finish_with_message(formatted_finish_msg); + pb.finish_with_message(finish_msg.to_string()); result } - -pub fn verify_sha1(data: &[u8], expected_hex: &str) -> bool { - let mut hasher = Sha1::new(); - hasher.update(data); - hex::encode(hasher.finalize()) == expected_hex -} - -pub fn verify_sha512(data: &[u8], expected_hex: &str) -> bool { - let mut hasher = Sha512::new(); - hasher.update(data); - hex::encode(hasher.finalize()) == expected_hex -}