diff --git a/Cargo.lock b/Cargo.lock index 0de219d..51dbfe6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,7 +429,6 @@ dependencies = [ "toml", "ureq", "url", - "urlencoding", "walkdir", ] @@ -1381,12 +1380,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" diff --git a/Cargo.toml b/Cargo.toml index 09ce1ca..60ab5d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,4 @@ thiserror = "2.0.18" toml = "1.0.3" ureq = { version = "3.2.0", features = ["json"] } url = "2.5.8" -urlencoding = "2.1.3" walkdir = "2.5.0" diff --git a/src/main.rs b/src/main.rs index 92b4d6b..7371bb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ use anyhow::anyhow; use clap::Parser; -use log::{debug, error}; +use log::debug; use std::{error::Error, path::Path}; use crate::{ activator::activate_instance, cli::Args, instance::{files_to_install_mod, insert_mod_to_instance}, - nexus::{NXMUrl, NexusAPI, download_mod}, + nexus::{NexusAPI,download_nxm}, types::RootConfig, }; @@ -63,16 +63,16 @@ fn command_order(root_config: &RootConfig, instance_id: &str) -> anyhow::Result< Ok(()) } -fn command_download(api_key: &str, nxm_url: &str) -> anyhow::Result<()> { - let api = NexusAPI::new(api_key); - let Some(parsed_url) = NXMUrl::parse_url(nxm_url) else { - error!("Failed to parse url"); - return Ok(()); +fn command_download(root_config: &RootConfig, nxm_url: &str) -> anyhow::Result<()> { + let Some(dl_location) = root_config.download_location() else { + return Err(anyhow!("No download location set")); }; - let links = api.generate_download_link_for_file(&parsed_url)?; + let Some(api_key) = root_config.nexus_api_key() else { + return Err(anyhow!("No API key provided")); + }; - download_mod(links.first().unwrap(), "./data/mod.7z")?; + download_nxm(api_key, nxm_url, dl_location)?; Ok(()) } @@ -108,7 +108,7 @@ fn main() -> Result<(), Box> { api.validate_key()?; } cli::Commands::Download { url } => { - command_download(root_config.nexus_api_key().unwrap(), &url)?; + command_download(&root_config, &url)?; } } diff --git a/src/nexus.rs b/src/nexus.rs index addab98..575e835 100644 --- a/src/nexus.rs +++ b/src/nexus.rs @@ -1,7 +1,7 @@ -mod url; mod api; mod downloader; +mod url; -pub use url::NXMUrl; pub use api::NexusAPI; -pub use downloader::download_mod; +pub use downloader::download_nxm; +pub use url::NXMUrl; diff --git a/src/nexus/api.rs b/src/nexus/api.rs index e72c33f..f784fce 100644 --- a/src/nexus/api.rs +++ b/src/nexus/api.rs @@ -1,5 +1,4 @@ use serde::Deserialize; -use ureq::http::Uri; use url::Url; use crate::nexus::NXMUrl; @@ -28,6 +27,20 @@ impl NexusAPI { Ok(()) } + pub fn mod_info(&self, game_name: &str, mod_id: &str) -> anyhow::Result { + let mod_info: ModInfo = ureq::get(format!( + "{}/v1/games/{}/mods/{}.json", + NEXUS_ENDPOINT, game_name, mod_id + )) + .header("apikey", &self.api_key) + .header("accept", "application/json") + .call()? + .body_mut() + .read_json()?; + + Ok(mod_info) + } + pub fn generate_download_link_for_file( &self, url: &NXMUrl, @@ -64,3 +77,38 @@ pub struct DownloadLocation { pub uri: String, } +impl DownloadLocation { + pub fn parse_url(&self) -> Result { + Url::parse(&self.uri) + } +} + +#[derive(Debug, Deserialize)] +pub struct ModInfo { + pub name: String, + // pub summary: String, + // pub description: String, + // pub picture_url: String, + // pub mod_downloads: u64, + // pub mod_unique_downloads: u64, + // pub uid: u64, + pub mod_id: u64, + // pub game_id: u64, + // pub allow_rating: bool, + // pub domain_name: String, + // pub category_id: u64, + pub version: String, + // pub endorsement_count: u64, + // pub created_timestamp: u64, + // pub created_time: String, + pub updated_timestamp: u64, + pub updated_time: String, + // pub author: String, + // pub uploaded_by: String, + // pub uploaded_users_profile_url: String, + // pub contains_adult_content: bool, + // pub status: String, + // pub available: bool, + // pub user: String /* Complex struct */, + // pub endorsement: String /* Complex struct*/ , +} diff --git a/src/nexus/downloader.rs b/src/nexus/downloader.rs index 61bd356..8b26963 100644 --- a/src/nexus/downloader.rs +++ b/src/nexus/downloader.rs @@ -1,20 +1,46 @@ -use std::{fs::File, io, path::Path}; +use std::{ + fs::{File, create_dir_all}, + io, + path::{Path, PathBuf}, +}; use anyhow::anyhow; use log::info; -use urlencoding::encode; -use crate::nexus::api::DownloadLocation; +use crate::nexus::{ + NXMUrl, NexusAPI, + api::{DownloadLocation, ModInfo}, +}; -pub fn download_mod( - mod_dl_link: &DownloadLocation, - target: impl AsRef, -) -> anyhow::Result<()> { - let encoded = encode(&mod_dl_link.uri).to_string(); +pub fn download_nxm(api_key: &str, link: &str, target_dir: impl AsRef) -> anyhow::Result<()> { + let api = NexusAPI::new(api_key); + let Some(parsed_url) = NXMUrl::parse_url(link) else { + return Err(anyhow!("Failed to parse url")); + }; + let mod_info = api.mod_info(&parsed_url.game, &parsed_url.mod_id)?; + let links = api.generate_download_link_for_file(&parsed_url)?; + let selected_mirror = links.first().unwrap(); + let url = selected_mirror.parse_url()?; + let original_filename = url.path_segments().and_then(|mut e| e.next_back()).unwrap(); + let filename = gen_filename_for_mod(&mod_info, original_filename); + let download_path = target_dir.as_ref().join(parsed_url.game).join(filename); + + if let Some(parent) = download_path.parent() { + create_dir_all(parent)?; + } + + download_mod(selected_mirror, download_path)?; + + Ok(()) +} + +fn download_mod(mod_dl_link: &DownloadLocation, target: impl AsRef) -> anyhow::Result<()> { info!("Downloading file from {}", mod_dl_link.name); - let (response, body) = ureq::get(&encoded).call()?.into_parts(); + let (response, body) = ureq::get(mod_dl_link.parse_url()?.as_str()) + .call()? + .into_parts(); if !response.status.is_success() { return Err(anyhow!("Got status {}", response.status)); @@ -27,3 +53,13 @@ pub fn download_mod( Ok(()) } + +fn gen_filename_for_mod(mod_info: &ModInfo, dl_filename: &str) -> String { + let filename_from_url = PathBuf::from(dl_filename); + let ext = filename_from_url + .extension() + .unwrap_or_default() + .to_string_lossy(); + + format!("{}-{}.{}", mod_info.mod_id, mod_info.version, ext) +}