Fetch file version from Nexus #147
4 changed files with 71 additions and 3 deletions
|
@ -17,6 +17,7 @@
|
||||||
- dtmm: reset dtkit-patch installations
|
- dtmm: reset dtkit-patch installations
|
||||||
- sdk: implement decompiling Lua files
|
- sdk: implement decompiling Lua files
|
||||||
- dtmm: fetch cover image for Nexus mods
|
- dtmm: fetch cover image for Nexus mods
|
||||||
|
- dtmm: fetch file version for Nexus mods
|
||||||
|
|
||||||
=== Fixed
|
=== Fixed
|
||||||
|
|
||||||
|
|
|
@ -411,7 +411,7 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result<Mod
|
||||||
.wrap_err_with(|| format!("Failed to read file {}", info.path.display()))?;
|
.wrap_err_with(|| format!("Failed to read file {}", info.path.display()))?;
|
||||||
let data = Cursor::new(data);
|
let data = Cursor::new(data);
|
||||||
|
|
||||||
let nexus = if let Some((_, id, version, _)) = info
|
let nexus = if let Some((_, id, version, timestamp)) = info
|
||||||
.path
|
.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
|
@ -423,9 +423,23 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result<Mod
|
||||||
.mods_id(id)
|
.mods_id(id)
|
||||||
.await
|
.await
|
||||||
.wrap_err_with(|| format!("Failed to query mod {} from Nexus", id))?;
|
.wrap_err_with(|| format!("Failed to query mod {} from Nexus", id))?;
|
||||||
let info = NexusInfo::from(mod_info);
|
|
||||||
|
|
||||||
tracing::debug!("{:?}", info);
|
let version = match api.file_version(id, timestamp).await {
|
||||||
|
Ok(version) => version,
|
||||||
|
Err(err) => {
|
||||||
|
let err = Report::new(err);
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to fetch version for Nexus download. \
|
||||||
|
Falling back to file name:\n{:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
version
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let info = NexusInfo::from(mod_info);
|
||||||
|
tracing::debug!(version, ?info);
|
||||||
|
|
||||||
Some((info, version))
|
Some((info, version))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -39,6 +39,8 @@ pub enum Error {
|
||||||
Infallible(#[from] Infallible),
|
Infallible(#[from] Infallible),
|
||||||
#[error("invalid NXM URL '{}': {0}", .1.as_str())]
|
#[error("invalid NXM URL '{}': {0}", .1.as_str())]
|
||||||
InvalidNXM(&'static str, Url),
|
InvalidNXM(&'static str, Url),
|
||||||
|
#[error("{0}")]
|
||||||
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
@ -102,6 +104,28 @@ impl Api {
|
||||||
self.send(req).await
|
self.send(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub async fn file_version<T>(&self, id: u64, timestamp: T) -> Result<String>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug,
|
||||||
|
OffsetDateTime: PartialEq<T>,
|
||||||
|
{
|
||||||
|
let url = BASE_URL_GAME.join(&format!("mods/{id}/files.json"))?;
|
||||||
|
let req = self.client.get(url);
|
||||||
|
let files: FileList = self.send(req).await?;
|
||||||
|
|
||||||
|
let Some(file) = files
|
||||||
|
.files
|
||||||
|
.into_iter()
|
||||||
|
.find(|file| file.uploaded_timestamp == timestamp)
|
||||||
|
else {
|
||||||
|
let err = Error::Custom("Timestamp does not match any file".into());
|
||||||
|
return Err(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(file.version)
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn picture(&self, url: impl IntoUrl + std::fmt::Debug) -> Result<Vec<u8>> {
|
pub async fn picture(&self, url: impl IntoUrl + std::fmt::Debug) -> Result<Vec<u8>> {
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let res = self.client.get(url).send().await?.error_for_status()?;
|
||||||
|
|
|
@ -64,6 +64,35 @@ pub struct Mod {
|
||||||
// pub contains_adult_content: bool,
|
// pub contains_adult_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct File {
|
||||||
|
pub id: Vec<u64>,
|
||||||
|
pub uid: u64,
|
||||||
|
pub file_id: u64,
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub category_id: u64,
|
||||||
|
pub category_name: String,
|
||||||
|
pub is_primary: bool,
|
||||||
|
pub size: u64,
|
||||||
|
pub file_name: String,
|
||||||
|
#[serde(with = "time::serde::timestamp")]
|
||||||
|
pub uploaded_timestamp: OffsetDateTime,
|
||||||
|
pub mod_version: String,
|
||||||
|
pub external_virus_scan_url: String,
|
||||||
|
pub description: String,
|
||||||
|
pub size_kb: u64,
|
||||||
|
pub size_in_bytes: u64,
|
||||||
|
pub changelog_html: Option<String>,
|
||||||
|
pub content_preview_link: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct FileList {
|
||||||
|
pub files: Vec<File>,
|
||||||
|
// pub file_updates: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct DownloadLink {
|
pub struct DownloadLink {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
Loading…
Add table
Reference in a new issue