spigot_rs/
lib.rs

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}