2024-08-17 16:11:04 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-08-17 22:34:06 +01:00
|
|
|
use std::{collections::HashSet, error::Error};
|
2024-08-17 16:11:04 +01:00
|
|
|
|
|
|
|
use super::PinnedMod;
|
2024-08-17 22:34:06 +01:00
|
|
|
use crate::{
|
|
|
|
mod_meta::{ModMeta, ModProvider},
|
2024-08-18 01:16:23 +01:00
|
|
|
modpack::{ModLoader, ModpackMeta},
|
2024-08-17 22:34:06 +01:00
|
|
|
providers::FileSource,
|
|
|
|
};
|
2024-08-17 16:11:04 +01:00
|
|
|
|
|
|
|
pub struct Modrinth {
|
|
|
|
client: reqwest::Client,
|
|
|
|
}
|
|
|
|
|
2024-08-17 22:34:06 +01:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct DonationUrls1 {
|
|
|
|
id: String,
|
|
|
|
platform: String,
|
|
|
|
url: String,
|
|
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct Gallery1 {
|
|
|
|
created: String,
|
|
|
|
description: String,
|
|
|
|
featured: bool,
|
|
|
|
ordering: i64,
|
|
|
|
title: String,
|
|
|
|
url: String,
|
|
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct License1 {
|
|
|
|
id: String,
|
|
|
|
name: String,
|
|
|
|
url: String,
|
|
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct ModrinthProject {
|
|
|
|
slug: String,
|
|
|
|
}
|
|
|
|
|
2024-08-17 16:11:04 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct VersionDeps {
|
|
|
|
dependency_type: String,
|
|
|
|
project_id: String,
|
|
|
|
file_name: Option<String>,
|
|
|
|
version_id: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct VersionHashes {
|
|
|
|
sha1: String,
|
|
|
|
sha512: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct VersionFiles {
|
|
|
|
// file_type: String,
|
|
|
|
filename: String,
|
|
|
|
hashes: VersionHashes,
|
|
|
|
// primary: bool,
|
|
|
|
// size: i64,
|
|
|
|
url: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct ModrinthProjectVersion {
|
2024-08-18 00:45:14 +01:00
|
|
|
// author_id: String,
|
|
|
|
// date_published: String,
|
|
|
|
dependencies: Option<Vec<VersionDeps>>,
|
2024-08-17 16:11:04 +01:00
|
|
|
// downloads: i64,
|
|
|
|
files: Vec<VersionFiles>,
|
|
|
|
// loaders: Vec<String>,
|
2024-08-18 00:45:14 +01:00
|
|
|
// name: String,
|
|
|
|
// project_id: String,
|
2024-08-17 22:34:06 +01:00
|
|
|
id: String,
|
2024-08-18 00:45:14 +01:00
|
|
|
version_number: String,
|
2024-08-17 16:11:04 +01:00
|
|
|
// version_type: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Modrinth {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-17 22:34:06 +01:00
|
|
|
pub async fn get_project_slug(&self, project_id: &str) -> Result<String, Box<dyn Error>> {
|
|
|
|
let mut project: ModrinthProject = self
|
|
|
|
.client
|
|
|
|
.get(format!("https://api.modrinth.com/v2/project/{project_id}"))
|
|
|
|
.send()
|
|
|
|
.await?
|
|
|
|
.json()
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(project.slug)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_mod_meta(
|
|
|
|
&self,
|
|
|
|
project_id: &str,
|
|
|
|
project_version: Option<&str>,
|
|
|
|
pack_meta: &ModpackMeta,
|
2024-08-18 01:16:23 +01:00
|
|
|
loader_override: Option<ModLoader>,
|
|
|
|
game_version_override: Option<String>,
|
2024-08-17 22:34:06 +01:00
|
|
|
) -> Result<ModMeta, Box<dyn Error>> {
|
2024-08-18 00:45:14 +01:00
|
|
|
let project_versions = self
|
2024-08-18 01:16:23 +01:00
|
|
|
.get_project_versions(
|
|
|
|
project_id,
|
|
|
|
pack_meta,
|
|
|
|
true,
|
|
|
|
loader_override,
|
|
|
|
game_version_override,
|
|
|
|
)
|
2024-08-18 00:45:14 +01:00
|
|
|
.await?;
|
2024-08-17 22:34:06 +01:00
|
|
|
let project_slug = self.get_project_slug(project_id).await?;
|
|
|
|
|
2024-08-18 00:45:14 +01:00
|
|
|
for version in project_versions.iter() {
|
2024-08-17 22:34:06 +01:00
|
|
|
if project_version.is_none() || project_version.unwrap_or("*") == version.id {
|
|
|
|
return Ok(ModMeta::new(&project_slug)?
|
|
|
|
.provider(ModProvider::Modrinth)
|
|
|
|
.version(&version.version_number.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(format!(
|
|
|
|
"Couldn't find project '{}' with version '{}'",
|
|
|
|
project_id,
|
|
|
|
project_version.unwrap_or("*")
|
|
|
|
)
|
|
|
|
.into())
|
|
|
|
}
|
|
|
|
|
2024-08-17 16:11:04 +01:00
|
|
|
/// Resolve a list of mod candidates in order of newest to oldest
|
|
|
|
pub async fn resolve(
|
|
|
|
&self,
|
|
|
|
mod_meta: &ModMeta,
|
|
|
|
pack_meta: &ModpackMeta,
|
2024-08-17 21:06:31 +01:00
|
|
|
) -> Result<PinnedMod, Box<dyn Error>> {
|
2024-08-18 00:45:14 +01:00
|
|
|
let versions = self
|
2024-08-18 01:16:23 +01:00
|
|
|
.get_project_versions(
|
|
|
|
&mod_meta.name,
|
|
|
|
pack_meta,
|
|
|
|
false,
|
|
|
|
mod_meta.loader.clone(),
|
|
|
|
mod_meta.mc_version.clone(),
|
|
|
|
)
|
2024-08-18 00:45:14 +01:00
|
|
|
.await?;
|
2024-08-17 16:11:04 +01:00
|
|
|
|
2024-08-17 21:06:31 +01:00
|
|
|
let package = if mod_meta.version == "*" {
|
2024-08-18 00:45:14 +01:00
|
|
|
versions.first().ok_or(format!(
|
2024-08-17 21:17:59 +01:00
|
|
|
"Cannot find package {} for loader={} and mc version={}",
|
|
|
|
mod_meta.name,
|
|
|
|
pack_meta.modloader.to_string().to_lowercase(),
|
|
|
|
pack_meta.mc_version
|
|
|
|
))?
|
2024-08-17 21:06:31 +01:00
|
|
|
} else {
|
|
|
|
versions
|
|
|
|
.iter()
|
2024-08-18 00:45:14 +01:00
|
|
|
.filter(|v| v.version_number == mod_meta.version)
|
2024-08-17 21:06:31 +01:00
|
|
|
.nth(0)
|
|
|
|
.ok_or(format!(
|
|
|
|
"Cannot find package {}@{}",
|
|
|
|
mod_meta.name, mod_meta.version
|
|
|
|
))?
|
|
|
|
};
|
2024-08-17 16:11:04 +01:00
|
|
|
|
2024-08-17 22:34:06 +01:00
|
|
|
let mut deps_meta = HashSet::new();
|
2024-08-18 00:45:14 +01:00
|
|
|
if let Some(deps) = &package.dependencies {
|
|
|
|
for dep in deps.iter().filter(|dep| dep.dependency_type == "required") {
|
|
|
|
deps_meta.insert(
|
2024-08-18 01:16:23 +01:00
|
|
|
self.get_mod_meta(
|
|
|
|
&dep.project_id,
|
|
|
|
dep.version_id.as_deref(),
|
|
|
|
pack_meta,
|
|
|
|
mod_meta.loader.clone(),
|
|
|
|
mod_meta.mc_version.clone(),
|
|
|
|
)
|
|
|
|
.await?,
|
2024-08-18 00:45:14 +01:00
|
|
|
);
|
|
|
|
}
|
2024-08-17 22:34:06 +01:00
|
|
|
}
|
|
|
|
|
2024-08-17 21:06:31 +01:00
|
|
|
Ok(PinnedMod {
|
|
|
|
source: package
|
|
|
|
.files
|
|
|
|
.iter()
|
|
|
|
.map(|f| FileSource::Download {
|
|
|
|
url: f.url.clone(),
|
|
|
|
sha1: f.hashes.sha1.clone(),
|
|
|
|
sha512: f.hashes.sha512.clone(),
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
version: package.version_number.clone(),
|
2024-08-18 00:45:14 +01:00
|
|
|
deps: if package
|
|
|
|
.dependencies
|
|
|
|
.as_ref()
|
|
|
|
.is_some_and(|deps| deps.len() > 0)
|
|
|
|
{
|
2024-08-17 22:34:06 +01:00
|
|
|
Some(deps_meta)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
2024-08-17 21:06:31 +01:00
|
|
|
})
|
2024-08-17 16:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_project_versions(
|
|
|
|
&self,
|
|
|
|
mod_id: &str,
|
|
|
|
pack_meta: &ModpackMeta,
|
2024-08-18 00:45:14 +01:00
|
|
|
ignore_game_version_and_loader: bool, // For deps we might as well let them use anything
|
2024-08-18 01:16:23 +01:00
|
|
|
loader_override: Option<ModLoader>,
|
|
|
|
game_version_override: Option<String>,
|
2024-08-17 16:11:04 +01:00
|
|
|
) -> Result<Vec<ModrinthProjectVersion>, Box<dyn Error>> {
|
2024-08-18 01:16:23 +01:00
|
|
|
let loader = loader_override
|
|
|
|
.unwrap_or(pack_meta.modloader.clone())
|
|
|
|
.to_string()
|
|
|
|
.to_lowercase();
|
|
|
|
let game_version = game_version_override.unwrap_or(pack_meta.mc_version.clone());
|
2024-08-18 00:45:14 +01:00
|
|
|
let query_vec = if ignore_game_version_and_loader {
|
|
|
|
&vec![]
|
|
|
|
} else {
|
|
|
|
&vec![
|
2024-08-18 01:16:23 +01:00
|
|
|
("loaders", format!("[\"{}\"]", loader)),
|
|
|
|
("game_versions", format!("[\"{}\"]", game_version)),
|
2024-08-18 00:45:14 +01:00
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut project_versions: Vec<ModrinthProjectVersion> = self
|
|
|
|
.client
|
|
|
|
.get(format!(
|
|
|
|
"https://api.modrinth.com/v2/project/{mod_id}/version"
|
|
|
|
))
|
|
|
|
.query(query_vec)
|
2024-08-17 16:11:04 +01:00
|
|
|
.send()
|
|
|
|
.await?
|
|
|
|
.json()
|
|
|
|
.await?;
|
|
|
|
|
2024-08-17 22:34:06 +01:00
|
|
|
project_versions.sort_by_key(|v| v.version_number.clone());
|
|
|
|
project_versions.reverse();
|
|
|
|
|
|
|
|
Ok(project_versions)
|
2024-08-17 16:11:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Modrinth {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
client: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|