use chrono::{DateTime, FixedOffset};
use reqwest::Client;
use serde::Deserialize;
use crate::{Modloader, List, error::{MLE, MLError, ErrorType}};
#[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,
}
async fn get(api: String, path: String) -> 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: String, 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: String, ids: Vec<String>) -> Vec<Project> {
println!("\tGet versions from modrinth\n");
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()
}
pub async fn versions(api: String, id: String, list: List) -> Vec<Version> {
let loaderstr = match list.modloader {
Modloader::Forge => String::from("forge"),
Modloader::Fabric => String::from("fabric"),
};
let url = format!(r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, id, loaderstr, list.mc_version);
let data = get(api, url).await.unwrap();
match data {
Some(data) => serde_json::from_slice(&data).unwrap(),
None => Vec::new(),
}
}
pub async fn get_raw_versions(api: String, versions: Vec<String>) -> Vec<Version> {
println!("Getting versions {}", &versions.join(", "));
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();
println!("\t └New current version: {}", times[0].0);
Ok(times[0].0.to_string())
},
_ => panic!("available_versions should never be negative"),
}
}
pub enum MCVersionType {
Release,
Latest,
Specific,
}
#[derive(Debug, Deserialize)]
pub struct MCVersion {
pub version: String,
pub version_type: String,
pub date: String,
pub major: bool,
}
pub async fn get_minecraft_version(api: String, version: MCVersionType) -> String {
let data = get(api, String::from("tag/game_version")).await.unwrap().unwrap();
let mc_versions: Vec<MCVersion> = serde_json::from_slice(&data).unwrap();
let ver = match version {
MCVersionType::Release => {
let mut i = 0;
while !mc_versions[i].major {
i += 1;
};
&mc_versions[i]
},
MCVersionType::Latest => &mc_versions[0],
MCVersionType::Specific => {
println!("Not inplemented");
&mc_versions[0]
}
};
String::from(&ver.version)
}