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
224
225
226
227
228
229
230
231
232
233
|
use chrono::{DateTime, FixedOffset};
use reqwest::Client;
use serde::Deserialize;
use crate::{
error::{ErrorType, MLError, MLE},
List, Modloader,
};
#[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: &str, 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: &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 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(),
}
}
///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 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: &str, 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)
}
|