hangar_api/
object.rs

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/// for some reason sorting is.. backwards by default? and there's no mention of this in the api documentation
31#[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, // this one *isn't* inverted though..
50}
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	/// The unique name of the project
114	pub name: String,
115	/// The namespace of the project
116	pub namespace: Namespace,
117	/// Stats of the project
118	pub stats: ProjectStats,
119	/// The category of the project
120	pub category: Category,
121	/// The last time the project was updated
122	#[serde(deserialize_with = "time::serde::rfc3339::deserialize")]
123	pub last_updated: OffsetDateTime,
124	/// The visibility of a project or version
125	pub visibility: Visibility,
126	/// The url to the project's icon
127	pub avatar_url: String,
128	/// The short description of the project
129	pub description: String,
130	/// Information about your interactions with the project
131	pub user_actions: UserActions,
132	/// The settings of the project
133	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/// The visibility of a project or version
160#[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	/// Type of the link. Either SIDEBAR or TOP
191	#[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	/// they don't follow their own schema.. this is supposed to be required
202	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		/// Hangar download url if not an external download
326		download_url: String,
327	},
328	#[serde(rename_all = "camelCase")]
329	External {
330		/// External download url if not directly uploaded to Hangar
331		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	/// Name of the plugin dependency. For non-external dependencies, this should be the Hangar project name
347	pub name: String,
348	/// Whether the dependency is required for the plugin to function
349	pub required: bool,
350	/// External url to download the dependency from if not a Hangar project, else null
351	pub external_url: Option<String>,
352	/// Server platform
353	pub platform: Platform,
354}