use chrono::{DateTime, FixedOffset};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::{
error::{ErrorType, MLError, 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,
}
async fn get(
api: &str,
path: &str,
) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
let url = format!(r#"{api}{path}"#);
let client = Client::builder()
.user_agent(format!(
"fxqnlr/modlistcli/{} ([email protected])",
env!("CARGO_PKG_VERSION")
))
.build()?;
let res = client.get(url).send().await?;
let mut data: Option<Vec<u8>> = None;
if res.status() == 200 {
data = Some(res.bytes().await?.to_vec());
}
Ok(data)
}
pub async fn project(api: &str, name: &str) -> Project {
let url = format!("project/{name}");
let data = get(api, &url).await.unwrap().unwrap();
serde_json::from_slice(&data).unwrap()
}
pub async fn projects(api: &str, ids: Vec<String>) -> Vec<Project> {
let all = ids.join(r#"",""#);
let url = format!(r#"projects?ids=["{all}"]"#);
let data = get(api, &url).await.unwrap().unwrap();
serde_json::from_slice(&data).unwrap()
}
///Get applicable versions from `mod_id` with list context
pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
let url = format!(
r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#,
id, list.modloader, list.mc_version
);
let data = get(api, &url).await.unwrap();
match data {
Some(data) => serde_json::from_slice(&data).unwrap(),
None => Vec::new(),
}
}
///Get version with the version ids
pub async fn get_raw_versions(
api: &str,
versions: Vec<String>,
) -> Vec<Version> {
let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
let data = get(api, &url).await.unwrap().unwrap();
serde_json::from_slice(&data).unwrap()
}
pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> {
match versions.len() {
0 => Err(MLError::new(ErrorType::ModError, "NO_VERSIONS_AVAILABLE")),
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())
}
_ => panic!("available_versions should never be negative"),
}
}
pub async fn get_game_versions() -> Vec<GameVersion> {
let data = get("https://api.modrinth.com/v2/", "tag/game_version")
.await
.unwrap()
.unwrap();
serde_json::from_slice(&data).unwrap()
}