diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 4d1861c..ee02b6c 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,7 @@ - dtmt: add utility to migrate mod projects - dtmm: reset dtkit-patch installations - sdk: implement decompiling Lua files +- dtmm: fetch cover image for Nexus mods === Fixed diff --git a/crates/dtmm/src/controller/import.rs b/crates/dtmm/src/controller/import.rs index 131d01f..5ca4068 100644 --- a/crates/dtmm/src/controller/import.rs +++ b/crates/dtmm/src/controller/import.rs @@ -27,6 +27,16 @@ fn find_archive_file( path } +fn image_data_to_buffer(data: impl AsRef<[u8]>) -> Result { + // Druid somehow doesn't return an error compatible with eyre, here. + // So we have to wrap through `Display` manually. + ImageBuf::from_data(data.as_ref()).map_err(|err| { + Report::msg(err.to_string()) + .wrap_err("Invalid image data") + .suggestion("Supported formats are: PNG, JPEG, Bitmap and WebP") + }) +} + // Runs the content of a `.mod` file to extract what data we can get // from legacy mods. // 1. Create a global function `new_mod` that stores @@ -234,6 +244,8 @@ fn extract_mod_config(archive: &mut ZipArchive) -> Result<(Mo None }; + tracing::debug!(?legacy_mod_data); + if let Some(name) = find_archive_file(archive, "dtmt.cfg") { let mut f = archive .by_name(&name) @@ -266,6 +278,24 @@ fn extract_mod_config(archive: &mut ZipArchive) -> Result<(Mo Ok((cfg, root)) } + } else if let Some((mod_id, resources, root)) = legacy_mod_data { + let cfg = ModConfig { + bundled: false, + dir: PathBuf::new(), + id: mod_id.clone(), + name: mod_id, + summary: "A mod for the game Warhammer 40,000: Darktide".into(), + version: "N/A".into(), + description: None, + author: None, + image: None, + categories: Vec::new(), + packages: Vec::new(), + resources, + depends: Vec::new(), + }; + + Ok((cfg, root)) } else { eyre::bail!( "Mod needs a config file or `.mod` file. \ @@ -404,6 +434,8 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result Result Result img, - Err(err) => { - let err = Report::msg(err.to_string()) - .wrap_err("Invalid image data") - .note("Supported formats are: PNG, JPEG, Bitmap and WebP") - .suggestion("Contact the mod author to fix this"); - return Err(err); - } - }; - + let img = image_data_to_buffer(buf)?; Some(img) + } else if let Some((nexus, _)) = &nexus { + let api = NexusApi::new(state.nexus_api_key.to_string())?; + let url = nexus.picture_url.as_ref(); + let data = api + .picture(url) + .await + .wrap_err_with(|| format!("Failed to download Nexus image from '{}'", url))?; + + let img = image_data_to_buffer(&data)?; + + let name = "image.bin"; + let path = dest.join(name); + match fs::write(&path, &data).await { + Ok(_) => { + mod_cfg.image = Some(name.into()); + Some(img) + } + Err(err) => { + let err = Report::new(err).wrap_err(format!( + "Failed to write Nexus picture to file '{}'", + path.display() + )); + tracing::error!("{:?}", err); + None + } + } } else { None }; tracing::trace!(?image); - - let mod_dir = state.data_dir.join(state.mod_dir.as_ref()); - let dest = mod_dir.join(&mod_cfg.id); - - tracing::trace!("Creating mods directory {}", dest.display()); - fs::create_dir_all(&dest) - .await - .wrap_err_with(|| format!("Failed to create data directory '{}'", dest.display()))?; + tracing::debug!(root, ?mod_cfg); let packages = if mod_cfg.bundled { extract_bundled_mod(&mut archive, root, &mod_dir).wrap_err("Failed to extract mod")? diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs index 64fdd28..e5b70c4 100644 --- a/crates/dtmm/src/state/data.rs +++ b/crates/dtmm/src/state/data.rs @@ -78,6 +78,7 @@ pub(crate) struct NexusInfo { pub author: String, pub summary: Arc, pub description: Arc, + pub picture_url: Arc, } impl From for NexusInfo { @@ -89,6 +90,7 @@ impl From for NexusInfo { author: value.author, summary: Arc::new(value.summary), description: Arc::new(value.description), + picture_url: Arc::new(value.picture_url.into()), } } } diff --git a/lib/nexusmods/src/lib.rs b/lib/nexusmods/src/lib.rs index 145435d..1407fca 100644 --- a/lib/nexusmods/src/lib.rs +++ b/lib/nexusmods/src/lib.rs @@ -4,7 +4,7 @@ use std::convert::Infallible; use lazy_static::lazy_static; use regex::Regex; use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue}; -use reqwest::{Client, RequestBuilder, Url}; +use reqwest::{Client, IntoUrl, RequestBuilder, Url}; use serde::Deserialize; use thiserror::Error; @@ -102,6 +102,16 @@ impl Api { self.send(req).await } + #[tracing::instrument(skip(self))] + pub async fn picture(&self, url: impl IntoUrl + std::fmt::Debug) -> Result> { + let res = self.client.get(url).send().await?.error_for_status()?; + + res.bytes() + .await + .map(|bytes| bytes.to_vec()) + .map_err(From::from) + } + pub fn parse_file_name>( name: S, ) -> Option<(String, u64, String, OffsetDateTime)> {