use chrono::{DateTime, FixedOffset}; use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::{ error::{EType, MLErr, MLE}, List, }; #[derive(Debug, Deserialize, Clone)] pub struct Project { pub slug: String, pub title: String, pub description: String, pub categories: Vec, pub client_side: Side, pub server_side: Side, pub body: String, pub additional_categories: Option>, pub project_type: Type, pub downloads: u32, pub icon_url: Option, pub id: String, pub team: String, pub moderator_message: Option, pub published: String, pub updated: String, pub approved: Option, pub followers: u32, pub status: Status, pub license: License, pub versions: Vec, } #[derive(Debug, Deserialize, Clone)] pub struct License { pub id: String, pub name: String, pub url: Option, } #[derive(Debug, Deserialize, Clone)] pub struct ModeratorMessage { pub message: String, pub body: Option, } #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Clone)] pub enum Side { required, optional, unsupported, } #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Clone)] pub enum Type { r#mod, modpack, recourcepack, } #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Clone)] pub enum Status { approved, rejected, draft, unlisted, archived, processing, unknown, } #[derive(Debug, Clone, Deserialize)] pub struct Version { pub name: String, pub version_number: String, pub changelog: Option, pub game_versions: Vec, pub version_type: VersionType, pub loaders: Vec, pub featured: bool, pub id: String, pub project_id: String, pub author_id: String, pub date_published: String, pub downloads: u32, pub files: Vec, } #[allow(non_camel_case_types)] #[derive(Debug, Clone, Deserialize)] pub enum VersionType { release, beta, alpha, } #[derive(Debug, Clone, Deserialize)] pub struct VersionFile { pub hashes: Hash, pub url: String, pub filename: String, pub primary: bool, pub size: u32, } #[derive(Debug, Clone, Deserialize)] pub struct Hash { pub sha512: String, pub sha1: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameVersion { pub version: String, pub version_type: GameVersionType, pub date: String, pub major: bool, } #[allow(non_camel_case_types)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum GameVersionType { release, snapshot, alpha, beta, } /// # Errors async fn get( api: &str, path: &str, ) -> Result>, Box> { let url = format!(r#"{api}{path}"#); let client = Client::builder() .user_agent(format!( "fxqnlr/modlist/{} (fxqnlr@gmail.com)", env!("CARGO_PKG_VERSION") )) .build()?; let res = client.get(url).send().await?; let mut data: Option> = None; if res.status() == 200 { data = Some(res.bytes().await?.to_vec()); } Ok(data) } /// # Errors pub async fn project(api: &str, name: &str) -> MLE { let url = format!("project/{name}"); let data = get(api, &url).await .map_err(|_| MLErr::new(EType::Other, "geterr"))? .ok_or(MLErr::new(EType::Other, "geterr2"))?; serde_json::from_slice(&data).map_err(|_| MLErr::new(EType::LibJson, "from project")) } /// # Errors pub async fn projects(api: &str, ids: Vec) -> MLE> { let all = ids.join(r#"",""#); let url = format!(r#"projects?ids=["{all}"]"#); let data = get(api, &url).await .map_err(|_| MLErr::new(EType::Other, "geterr"))? .ok_or(MLErr::new(EType::Other, "geterr2"))?; serde_json::from_slice(&data).map_err(|_| MLErr::new(EType::LibJson, "from projects")) } ///Get applicable versions from `mod_id` with list context /// # Errors pub async fn versions(api: &str, id: String, list: List) -> MLE> { let url = format!( r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, id, list.modloader, list.mc_version ); let data = get(api, &url).await .map_err(|_| MLErr::new(EType::Other, "geterr"))?; Ok(match data { Some(data) => serde_json::from_slice(&data).map_err(|_| MLErr::new(EType::LibJson, "from version"))?, None => Vec::new(), }) } ///Get version with the version ids /// # Errors pub async fn get_raw_versions( api: &str, versions: Vec, ) -> MLE> { let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); let data = get(api, &url).await .map_err(|_| MLErr::new(EType::Other, "geterr"))? .ok_or(MLErr::new(EType::Other, "geterr2"))?; serde_json::from_slice(&data).map_err(|_| MLErr::new(EType::LibJson, "from raw version")) } /// # Errors pub fn extract_current_version(versions: Vec) -> MLE { match versions.len() { 0 => Err(MLErr::new(EType::ModError, "NO_VERSIONS_AVAILABLE")), 1.. => { let mut times: Vec<(String, DateTime)> = vec![]; for ver in versions { let stamp = DateTime::parse_from_rfc3339(&ver.date_published)?; times.push((ver.id, stamp)); } times.sort_by_key(|t| t.1); times.reverse(); Ok(times[0].0.to_string()) } } } /// # Errors pub async fn get_game_versions() -> MLE> { let data = get("https://api.modrinth.com/v2/", "tag/game_version") .await .map_err(|_| MLErr::new(EType::Other, "geterr"))? .ok_or(MLErr::new(EType::Other, "geterr2"))?; serde_json::from_slice(&data).map_err(|_| MLErr::new(EType::LibJson, "from game version")) }