1use std::fmt::Display;
2
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5
6#[derive(Debug, Serialize)]
7pub struct Pagination {
8 pub limit: i64,
9 pub offset: i64,
10}
11
12impl Default for Pagination {
13 fn default() -> Self {
14 Self {
15 limit: 25,
16 offset: 0,
17 }
18 }
19}
20
21impl From<(i64, i64)> for Pagination {
22 fn from(value: (i64, i64)) -> Self {
23 Self {
24 limit: value.0,
25 offset: value.1,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, Serialize)]
32#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
33#[serde(rename_all = "snake_case")]
34pub enum ProjectsSort {
35 #[serde(rename = "-views")]
36 Views,
37 #[serde(rename = "-downloads")]
38 Downloads,
39 #[serde(rename = "-newest")]
40 Newest,
41 #[serde(rename = "-stars")]
42 Stars,
43 #[serde(rename = "-updated")]
44 Updated,
45 #[serde(rename = "-recent-downloads")]
46 RecentDownloads,
47 #[serde(rename = "-recent-views")]
48 RecentViews,
49 Slug, }
51
52#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
53#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
54#[serde(rename_all = "snake_case")]
55pub enum Category {
56 AdminTools,
57 Chat,
58 DevTools,
59 Economy,
60 Gameplay,
61 Games,
62 Protection,
63 RolePlaying,
64 WorldManagement,
65 Misc,
66 Undefined,
67}
68
69impl Display for Category {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 let s = match self {
72 Self::AdminTools => "Admin Tools",
73 Self::Chat => "Chat",
74 Self::DevTools => "Dev Tools",
75 Self::Economy => "Economy",
76 Self::Gameplay => "Gameplay",
77 Self::Games => "Games",
78 Self::Protection => "Protection",
79 Self::RolePlaying => "Role Playing",
80 Self::WorldManagement => "World Management",
81 Self::Misc => "Misc",
82 Self::Undefined => "Undefined",
83 };
84 write!(f, "{s}")
85 }
86}
87
88#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
89#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
90#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
91pub enum Platform {
92 Paper,
93 Waterfall,
94 Velocity,
95}
96
97impl Display for Platform {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 let s = match self {
100 Self::Paper => "Paper",
101 Self::Waterfall => "Waterfall",
102 Self::Velocity => "Velocity",
103 };
104 write!(f, "{s}")
105 }
106}
107
108#[derive(Debug, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct Project {
111 #[serde(deserialize_with = "time::serde::rfc3339::deserialize")]
112 pub created_at: OffsetDateTime,
113 pub name: String,
115 pub namespace: Namespace,
117 pub stats: ProjectStats,
119 pub category: Category,
121 #[serde(deserialize_with = "time::serde::rfc3339::deserialize")]
123 pub last_updated: OffsetDateTime,
124 pub visibility: Visibility,
126 pub avatar_url: String,
128 pub description: String,
130 pub user_actions: UserActions,
132 pub settings: ProjectSettings,
134}
135
136#[derive(Debug, Deserialize)]
137pub struct Namespace {
138 pub owner: String,
139 pub slug: String,
140}
141
142impl Namespace {
143 pub fn url(&self) -> String {
144 format!("https://hangar.papermc.io/{}/{}", self.owner, self.slug)
145 }
146}
147
148#[derive(Debug, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct ProjectStats {
151 pub views: i64,
152 pub downloads: i64,
153 pub recent_views: i64,
154 pub recent_downloads: i64,
155 pub stars: i64,
156 pub watchers: i64,
157}
158
159#[derive(Debug, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub enum Visibility {
163 Public,
164 New,
165 NeedsChanges,
166 NeedsApproval,
167 SoftDelete,
168}
169
170#[derive(Debug, Deserialize)]
171pub struct UserActions {
172 pub starred: bool,
173 pub watching: bool,
174 pub flagged: bool,
175}
176
177#[derive(Debug, Deserialize)]
178pub struct ProjectSettings {
179 pub links: Vec<Link>,
180 pub tags: Vec<ProjectTags>,
181 pub license: License,
182 pub keywords: Vec<String>,
183 pub sponsors: String,
184 pub donation: Donation,
185}
186
187#[derive(Debug, Deserialize)]
188pub struct Link {
189 pub id: i64,
190 #[serde(rename = "type")]
192 pub link_type: String,
193 pub title: Option<String>,
194 pub links: Vec<ActualLink>,
195}
196
197#[derive(Debug, Deserialize)]
198pub struct ActualLink {
199 pub id: i64,
200 pub name: String,
201 pub url: Option<String>,
203}
204
205#[derive(Debug, Deserialize)]
206#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
207pub enum ProjectTags {
208 Addon,
209 Library,
210 SupportsFolia,
211}
212
213#[derive(Debug, Deserialize)]
214pub struct License {
215 pub name: Option<String>,
216 pub url: Option<String>,
217 #[serde(rename = "type")]
218 pub license_type: String,
219}
220
221#[derive(Debug, Deserialize)]
222pub struct Donation {
223 pub enable: bool,
224 pub subject: String,
225}
226
227#[derive(Debug, Deserialize)]
228#[serde(rename_all = "camelCase")]
229pub struct Version {
230 #[serde(deserialize_with = "time::serde::rfc3339::deserialize")]
231 pub created_at: OffsetDateTime,
232 pub name: String,
233 pub visibility: Visibility,
234 pub description: String,
235 pub stats: VersionStats,
236 pub author: String,
237 pub review_state: ReviewState,
238 pub channel: Channel,
239 pub pinned_status: PinnedStatus,
240 pub downloads: ByPlatform<VersionDownloads>,
241 pub plugin_dependencies: ByPlatform<Vec<VersionPluginDependencies>>,
242 pub platform_dependencies: ByPlatform<Vec<String>>,
243 pub platform_dependencies_formatted: ByPlatform<Vec<String>>,
244}
245
246#[derive(Debug, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct VersionStats {
249 pub total_downloads: i64,
250 pub platform_downloads: ByPlatform<i64>,
251}
252
253#[derive(Debug, Deserialize)]
254pub struct ByPlatform<T> {
255 #[serde(rename = "PAPER")]
256 pub paper: Option<T>,
257 #[serde(rename = "WATERFALL")]
258 pub waterfall: Option<T>,
259 #[serde(rename = "VELOCITY")]
260 pub velocity: Option<T>,
261}
262
263impl<T> ByPlatform<T> {
264 pub fn get(&self, platform: Platform) -> Option<&T> {
265 match platform {
266 Platform::Paper => self.paper.as_ref(),
267 Platform::Waterfall => self.waterfall.as_ref(),
268 Platform::Velocity => self.velocity.as_ref(),
269 }
270 }
271
272 pub fn iter(&self) -> impl Iterator<Item = (Platform, &T)> {
273 self.paper
274 .iter()
275 .map(|v| (Platform::Paper, v))
276 .chain(self.waterfall.iter().map(|v| (Platform::Waterfall, v)))
277 .chain(self.velocity.iter().map(|v| (Platform::Velocity, v)))
278 }
279}
280
281#[derive(Debug, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub enum ReviewState {
284 Unreviewed,
285 Reviewed,
286 UnderReview,
287 PartiallyReviewed,
288}
289
290#[derive(Debug, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct Channel {
293 #[serde(deserialize_with = "time::serde::rfc3339::deserialize")]
294 pub created_at: OffsetDateTime,
295 pub name: String,
296 pub description: Option<String>,
297 pub color: String,
298 pub flags: Vec<ChannelFlags>,
299}
300
301#[derive(Debug, Deserialize)]
302#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
303pub enum ChannelFlags {
304 Frozen,
305 Unstable,
306 Pinned,
307 SendsNotifications,
308 HideByDefault,
309}
310
311#[derive(Debug, Deserialize)]
312#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
313pub enum PinnedStatus {
314 None,
315 Version,
316 Channel,
317}
318
319#[derive(Debug, Deserialize)]
320#[serde(untagged)]
321pub enum VersionDownloads {
322 #[serde(rename_all = "camelCase")]
323 Internal {
324 file_info: VersionDownloadsFileInfo,
325 download_url: String,
327 },
328 #[serde(rename_all = "camelCase")]
329 External {
330 external_url: String,
332 },
333}
334
335#[derive(Debug, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct VersionDownloadsFileInfo {
338 pub name: String,
339 pub size_bytes: i64,
340 pub sha256_hash: String,
341}
342
343#[derive(Debug, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct VersionPluginDependencies {
346 pub name: String,
348 pub required: bool,
350 pub external_url: Option<String>,
352 pub platform: Platform,
354}