summaryrefslogtreecommitdiff
path: root/src/apis/modrinth.rs
blob: afd7b693380507152546b112cee1d7ea899ae457 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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)?)
}