use chrono::{DateTime, FixedOffset};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::{
errors::{Error, MLE},
List,
};
#[derive(Debug, Deserialize, Clone)]
pub struct Project {
pub slug: String,
pub title: String,
pub description: String,
pub categories: Vec<String>,
pub client_side: Side,
pub server_side: Side,
pub body: String,
pub additional_categories: Option<Vec<String>>,
pub project_type: Type,
pub downloads: u32,
pub icon_url: Option<String>,
pub id: String,
pub team: String,
pub moderator_message: Option<ModeratorMessage>,
pub published: String,
pub updated: String,
pub approved: Option<String>,
pub followers: u32,
pub status: Status,
pub license: License,
pub versions: Vec<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct License {
pub id: String,
pub name: String,
pub url: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ModeratorMessage {
pub message: String,
pub body: Option<String>,
}
#[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<String>,
pub game_versions: Vec<String>,
pub version_type: VersionType,
pub loaders: Vec<String>,
pub featured: bool,
pub id: String,
pub project_id: String,
pub author_id: String,
pub date_published: String,
pub downloads: u32,
pub files: Vec<VersionFile>,
}
#[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<Vec<u8>, Error> {
let url = format!(r#"{api}{path}"#);
let client = Client::builder()
.user_agent(format!(
"fxqnlr/modlist/{} ([email protected])",
env!("CARGO_PKG_VERSION")
))
.build()?;
let res = client.get(url).send().await?;
if res.status() != 200 {
return Err(Error::RequestNotOK(res.status()));
}
Ok(res.bytes().await?.to_vec())
}
/// # Errors
pub async fn project(api: &str, name: &str) -> MLE<Project> {
let url = format!("project/{name}");
let data = get(api, &url).await?;
Ok(serde_json::from_slice(&data)?)
}
/// # Errors
pub async fn projects(api: &str, ids: Vec<String>) -> MLE<Vec<Project>> {
let all = ids.join(r#"",""#);
let url = format!(r#"projects?ids=["{all}"]"#);
let data = get(api, &url).await?;
Ok(serde_json::from_slice(&data)?)
}
///Get applicable versions from `mod_id` with list context
/// # Errors
pub async fn versions(api: &str, id: String, list: List) -> MLE<Vec<Version>> {
let url = format!(
r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#,
id, list.modloader, list.mc_version
);
let data = get(api, &url).await?;
Ok(if data.is_empty() {
Vec::new()
} else {
serde_json::from_slice(&data)?
})
}
///Get version with the version ids
/// # Errors
pub async fn get_raw_versions(
api: &str,
versions: Vec<String>,
) -> MLE<Vec<Version>> {
let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
let data = get(api, &url).await?;
Ok(serde_json::from_slice(&data)?)
}
/// # Errors
pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> {
match versions.len() {
0 => Err(Error::ModError("NO_VERSIONS_AVAILABLE".to_string())),
1.. => {
let mut times: Vec<(String, DateTime<FixedOffset>)> = 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<Vec<GameVersion>> {
let data = get("https://api.modrinth.com/v2/", "tag/game_version")
.await?;
Ok(serde_json::from_slice(&data)?)
}