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
|
use std::io::{Error, ErrorKind};
use chrono::{DateTime, FixedOffset};
use reqwest::Client;
use serde::Deserialize;
use crate::{Modloader, 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,
}
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 {
println!("!!!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!("!!!PROJECTS");
println!("Getting versions for all mods from modrinth");
let all = ids.join(r#"",""#);
let url = format!(r#"projects?ids=["{}"]"#, all);
//println!("{}", url);
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> {
println!("!!!VERSIONS");
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();
dbg!(&data);
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!("!!!RAWVERSIONS");
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>) -> Result<String, Box<dyn std::error::Error>> {
match versions.len() {
0 => Err(Box::new(Error::new(ErrorKind::NotFound, "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!("Current Version: {}", times[0].0);
Ok(times[0].0.to_string())
},
_ => panic!("available_versions should never be negative"),
}
}
|