1use reqwest::Client;
2use serde::Deserialize;
3
4#[derive(Debug, Deserialize, Clone)]
5pub struct Premium {
6 pub price: String,
7 pub currency: String,
8}
9
10#[derive(Debug, Deserialize, Clone)]
11pub struct ResourceAuthor {
12 pub id: String,
13 pub username: String,
14}
15
16#[derive(Debug, Deserialize, Clone)]
17pub struct Reviews {
18 pub unique: String,
19 pub total: String,
20}
21
22#[derive(Debug, Deserialize, Clone)]
23pub struct ResourceStats {
24 pub downloads: String,
25 pub updates: String,
26 pub reviews: Reviews,
27 pub rating: String,
28}
29
30#[derive(Debug, Deserialize, Clone)]
31pub struct Resource {
32 pub id: String,
33 pub title: String,
34 pub tag: String,
35 pub current_version: String,
36 pub native_minecraft_version: Option<String>,
37 pub supported_minecraft_versions: Option<Vec<String>>,
38 pub premium: Premium,
39 pub stats: ResourceStats,
40 pub description: String,
41 pub icon_link: String,
42 pub external_download_url: String,
43}
44
45#[derive(Debug, Deserialize, Clone)]
46pub struct ResourceCategory {
47 pub id: String,
48 pub title: String,
49 pub description: String,
50}
51
52#[derive(Debug, Deserialize, Clone)]
53pub struct ResourceUpdate {
54 pub id: String,
55 pub resource_id: String,
56 pub title: String,
57 pub message: String,
58}
59
60#[derive(Debug, Deserialize, Clone)]
61pub struct AuthorIdentities {
62 pub twitter: Option<String>,
63 pub discord: Option<String>,
64 pub github: Option<String>,
65}
66
67#[derive(Debug, Deserialize, Clone)]
68pub struct Author {
69 pub id: String,
70 pub username: String,
71 pub resource_count: String,
72 pub identities: AuthorIdentities,
73 pub avatar: String,
74}
75
76async fn request(url: String) -> anyhow::Result<String> {
77 let client = Client::builder()
78 .user_agent("Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0")
79 .build()?;
80 let request = client.get(url).build()?;
81
82 Ok(client
83 .execute(request)
84 .await?
85 .error_for_status()?
86 .text()
87 .await?)
88}
89
90async fn list_resources(url: String) -> anyhow::Result<Vec<Resource>> {
91 Ok(serde_json::from_str(&request(url).await?)?)
92}
93
94async fn list_resource_updates(url: String) -> anyhow::Result<Vec<ResourceUpdate>> {
95 Ok(serde_json::from_str(&request(url).await?)?)
96}
97
98pub async fn list_resources_in_category(category: usize) -> anyhow::Result<Vec<Resource>> {
99 list_resources(format!(
100 "https://api.spigotmc.org/simple/0.2/index.php?action=listResources&category={}",
101 category
102 ))
103 .await
104}
105
106pub async fn list_resources_in_category_page(
107 category: usize,
108 page: usize,
109) -> anyhow::Result<Vec<Resource>> {
110 list_resources(format!(
111 "https://api.spigotmc.org/simple/0.2/index.php?action=listResources&category={}&page={}",
112 category, page
113 ))
114 .await
115}
116
117pub async fn list_resources_page(page: usize) -> anyhow::Result<Vec<Resource>> {
118 list_resources(format!(
119 "https://api.spigotmc.org/simple/0.2/index.php?action=listResources&page={}",
120 page
121 ))
122 .await
123}
124
125pub async fn get_resource(id: usize) -> anyhow::Result<Resource> {
126 Ok(serde_json::from_str(
127 &request(format!(
128 "https://api.spigotmc.org/simple/0.2/index.php?action=getResource&id={}",
129 id
130 ))
131 .await?,
132 )?)
133}
134
135pub async fn get_resources_by_author(id: usize) -> anyhow::Result<Vec<Resource>> {
136 list_resources(format!(
137 "https://api.spigotmc.org/simple/0.2/index.php?action=getResourcesByAuthor&id={}",
138 id
139 ))
140 .await
141}
142
143pub async fn get_resources_by_author_page(id: usize, page: usize) -> anyhow::Result<Vec<Resource>> {
144 list_resources(format!(
145 "https://api.spigotmc.org/simple/0.2/index.php?action=getResourcesByAuthor&id={}&page={}",
146 id, page
147 ))
148 .await
149}
150
151pub async fn list_resource_categories() -> anyhow::Result<Vec<ResourceCategory>> {
152 Ok(serde_json::from_str(
153 &request(
154 "https://api.spigotmc.org/simple/0.2/index.php?action=listResourceCategories"
155 .to_string(),
156 )
157 .await?,
158 )?)
159}
160
161pub async fn get_resource_update(id: usize) -> anyhow::Result<ResourceUpdate> {
162 Ok(serde_json::from_str(
163 &request(format!(
164 "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdate&id={}",
165 id
166 ))
167 .await?,
168 )?)
169}
170
171pub async fn get_resource_updates(id: usize) -> anyhow::Result<Vec<ResourceUpdate>> {
172 list_resource_updates(format!(
173 "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdates&id={}",
174 id
175 ))
176 .await
177}
178
179pub async fn get_resource_updates_page(
180 id: usize,
181 page: usize,
182) -> anyhow::Result<Vec<ResourceUpdate>> {
183 list_resource_updates(format!(
184 "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdates&id={}&page={}",
185 id, page
186 ))
187 .await
188}
189
190pub async fn get_author(id: usize) -> anyhow::Result<Author> {
191 Ok(serde_json::from_str(
192 &request(format!(
193 "https://api.spigotmc.org/simple/0.2/index.php?action=getAuthor&id={}",
194 id
195 ))
196 .await?,
197 )?)
198}
199
200pub async fn find_author(name: &str) -> anyhow::Result<Author> {
201 Ok(serde_json::from_str(
202 &request(format!(
203 "https://api.spigotmc.org/simple/0.2/index.php?action=findAuthor&name={}",
204 name
205 ))
206 .await?,
207 )?)
208}
209
210#[cfg(test)]
211mod tests {
212 use crate::{
213 find_author, get_author, get_resource, get_resource_update, get_resource_updates,
214 get_resources_by_author, get_resources_by_author_page, list_resource_categories,
215 list_resources_in_category, list_resources_in_category_page, list_resources_page,
216 };
217
218 #[tokio::test]
219 async fn test_resources_list() -> anyhow::Result<()> {
220 list_resource_categories().await?;
221 Ok(())
222 }
223
224 #[tokio::test]
225 async fn test_resources_list_page() -> anyhow::Result<()> {
226 list_resources_page(2).await?;
227 Ok(())
228 }
229
230 #[tokio::test]
231 async fn test_resource_categories() -> anyhow::Result<()> {
232 list_resources_in_category(4).await?;
233 Ok(())
234 }
235
236 #[tokio::test]
237 async fn test_resource_categories_page() -> anyhow::Result<()> {
238 list_resources_in_category_page(4, 2).await?;
239 Ok(())
240 }
241
242 #[tokio::test]
243 async fn test_get_resource() -> anyhow::Result<()> {
244 get_resource(57242).await?;
245 Ok(())
246 }
247
248 #[tokio::test]
249 async fn test_get_resources_by_author() -> anyhow::Result<()> {
250 get_resources_by_author(1).await?;
251 Ok(())
252 }
253
254 #[tokio::test]
255 async fn test_get_resources_by_author_page() -> anyhow::Result<()> {
256 get_resources_by_author_page(1, 2).await?;
257
258 Ok(())
259 }
260
261 #[tokio::test]
262 async fn test_list_resource_categories() -> anyhow::Result<()> {
263 list_resource_categories().await?;
264
265 Ok(())
266 }
267
268 #[tokio::test]
269 async fn test_resource_updates() -> anyhow::Result<()> {
270 let res = get_resource_updates(57242).await?;
271 let update = res.last().unwrap();
272 let id = usize::from_str_radix(&update.id.clone(), 10).unwrap();
273
274 get_resource_update(id).await?;
275 Ok(())
276 }
277
278 #[tokio::test]
279 async fn test_get_author() -> anyhow::Result<()> {
280 get_author(1).await?;
281 Ok(())
282 }
283
284 #[tokio::test]
285 async fn test_find_author() -> anyhow::Result<()> {
286 find_author("md_5").await?;
287 Ok(())
288 }
289}