1use std::{
2 borrow::Cow,
3 fmt::{Display, Formatter, Result as FmtResult},
4};
5
6use hyper::{body::Bytes, StatusCode};
7use serde::{
8 de::{Error as DeError, IgnoredAny, MapAccess, Unexpected, Visitor},
9 Deserialize, Deserializer, Serialize,
10};
11use time::OffsetDateTime;
12
13use crate::{request::Requestable, util::datetime::deserialize_datetime, ClientError};
14
15#[derive(Clone, Debug, Deserialize)]
17pub struct RenderList {
18 pub renders: Vec<Render>,
20 #[serde(rename = "maxRenders")]
23 pub max_renders: u32,
24}
25
26impl Requestable for RenderList {
27 fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
28 ClientError::response_error(bytes, status.as_u16())
29 }
30}
31
32#[derive(Clone, Debug, Deserialize)]
33pub struct Render {
34 #[serde(rename = "renderID")]
35 pub id: u32,
36 #[serde(deserialize_with = "deserialize_datetime")]
37 pub date: OffsetDateTime,
38 pub username: Box<str>,
39 pub progress: Box<str>,
40 pub renderer: Box<str>,
41 pub description: Box<str>,
42 pub title: Box<str>,
43 #[serde(rename = "isBot")]
44 pub is_bot: bool,
45 #[serde(rename = "isVerified")]
46 pub is_verified: bool,
47 #[serde(rename = "videoUrl")]
48 pub video_url: Box<str>,
49 #[serde(rename = "mapLink")]
50 pub map_link: Box<str>,
51 #[serde(rename = "mapTitle")]
52 pub map_title: Box<str>,
53 #[serde(rename = "replayDifficulty")]
54 pub replay_difficulty: Box<str>,
55 #[serde(rename = "replayUsername")]
56 pub replay_username: Box<str>,
57 #[serde(rename = "mapID")]
58 pub map_id: u32,
59 #[serde(rename = "needToRedownload")]
60 pub need_to_redownload: bool,
61 #[serde(rename = "motionBlur960fps")]
62 pub motion_blur: bool,
63 #[serde(rename = "renderStartTime", deserialize_with = "deserialize_datetime")]
64 pub render_start_time: OffsetDateTime,
65 #[serde(rename = "renderEndTime", deserialize_with = "deserialize_datetime")]
66 pub render_end_time: OffsetDateTime,
67 #[serde(rename = "uploadEndTime", deserialize_with = "deserialize_datetime")]
68 pub upload_end_time: OffsetDateTime,
69 #[serde(rename = "renderTotalTime")]
70 pub render_total_time: u32,
71 #[serde(rename = "uploadTotalTime")]
72 pub upload_total_time: u32,
73 #[serde(rename = "mapLength")]
74 pub map_length: u32,
75 #[serde(rename = "replayMods")]
76 pub replay_mods: Box<str>,
77 pub removed: bool,
78 #[serde(flatten)]
79 pub options: RenderOptions,
80 #[serde(flatten)]
81 pub skin: RenderSkinOption<'static>,
82}
83
84#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
85pub enum RenderResolution {
86 #[serde(rename = "720x480")]
88 SD480,
89 #[serde(rename = "960x540")]
91 SD960,
92 #[serde(rename = "1280x720")]
94 HD720,
95 #[serde(rename = "1920x1080")]
97 HD1080,
98}
99
100impl RenderResolution {
101 #[must_use]
102 pub fn as_str(self) -> &'static str {
103 match self {
104 Self::SD480 => "720x480",
105 Self::SD960 => "960x540",
106 Self::HD720 => "1280x720",
107 Self::HD1080 => "1920x1080",
108 }
109 }
110}
111
112impl Display for RenderResolution {
113 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
114 f.write_str(self.as_str())
115 }
116}
117
118#[derive(Clone, Debug, Deserialize, Serialize)]
120pub struct RenderOptions {
121 pub resolution: RenderResolution,
122 #[serde(rename = "globalVolume")]
124 pub global_volume: u8,
125 #[serde(rename = "musicVolume")]
127 pub music_volume: u8,
128 #[serde(rename = "hitsoundVolume")]
130 pub hitsound_volume: u8,
131 #[serde(rename = "showHitErrorMeter")]
133 pub show_hit_error_meter: bool,
134 #[serde(rename = "showUnstableRate")]
136 pub show_unstable_rate: bool,
137 #[serde(rename = "showScore")]
139 pub show_score: bool,
140 #[serde(rename = "showHPBar")]
142 pub show_hp_bar: bool,
143 #[serde(rename = "showComboCounter")]
145 pub show_combo_counter: bool,
146 #[serde(rename = "showPPCounter")]
148 pub show_pp_counter: bool,
149 #[serde(rename = "showScoreboard")]
151 pub show_scoreboard: bool,
152 #[serde(rename = "showBorders")]
154 pub show_borders: bool,
155 #[serde(rename = "showMods")]
157 pub show_mods: bool,
158 #[serde(rename = "showResultScreen")]
160 pub show_result_screen: bool,
161 #[serde(rename = "useSkinCursor")]
163 pub use_skin_cursor: bool,
164 #[serde(rename = "useSkinColors")]
166 pub use_skin_colors: bool,
167 #[serde(rename = "useSkinHitsounds")]
169 pub use_skin_hitsounds: bool,
170 #[serde(rename = "useBeatmapColors")]
172 pub use_beatmap_colors: bool,
173 #[serde(rename = "cursorScaleToCS")]
175 pub cursor_scale_to_cs: bool,
176 #[serde(rename = "cursorRainbow")]
178 pub cursor_rainbow: bool,
179 #[serde(rename = "cursorTrailGlow")]
181 pub cursor_trail_glow: bool,
182 #[serde(rename = "drawFollowPoints")]
184 pub draw_follow_points: bool,
185 #[serde(rename = "scaleToTheBeat")]
187 pub beat_scaling: bool,
188 #[serde(rename = "sliderMerge")]
190 pub slider_merge: bool,
191 #[serde(rename = "objectsRainbow")]
193 pub objects_rainbow: bool,
194 #[serde(rename = "objectsFlashToTheBeat")]
196 pub flash_objects: bool,
197 #[serde(rename = "useHitCircleColor")]
199 pub use_slider_hitcircle_color: bool,
200 #[serde(rename = "seizureWarning")]
202 pub seizure_warning: bool,
203 #[serde(rename = "loadStoryboard")]
205 pub load_storyboard: bool,
206 #[serde(rename = "loadVideo")]
208 pub load_video: bool,
209 #[serde(rename = "introBGDim")]
211 pub intro_bg_dim: u8,
212 #[serde(rename = "inGameBGDim")]
214 pub ingame_bg_dim: u8,
215 #[serde(rename = "breakBGDim")]
217 pub break_bg_dim: u8,
218 #[serde(rename = "BGParallax")]
220 pub bg_parallax: bool,
221 #[serde(rename = "showDanserLogo")]
223 pub show_danser_logo: bool,
224 #[serde(rename = "skip")]
226 pub skip_intro: bool,
227 #[serde(rename = "cursorRipples")]
229 pub cursor_ripples: bool,
230 #[serde(rename = "cursorSize")]
232 pub cursor_size: f32,
233 #[serde(rename = "cursorTrail")]
235 pub cursor_trail: bool,
236 #[serde(rename = "drawComboNumbers")]
238 pub draw_combo_numbers: bool,
239 #[serde(rename = "sliderSnakingIn")]
241 pub slider_snaking_in: bool,
242 #[serde(rename = "sliderSnakingOut")]
244 pub slider_snaking_out: bool,
245 #[serde(rename = "showHitCounter")]
247 pub show_hit_counter: bool,
248 #[serde(rename = "showKeyOverlay")]
250 pub show_key_overlay: bool,
251 #[serde(rename = "showAvatarsOnScoreboard")]
254 pub show_avatars_on_scoreboard: bool,
255 #[serde(rename = "showAimErrorMeter")]
257 pub show_aim_error_meter: bool,
258 #[serde(rename = "playNightcoreSamples")]
260 pub play_nightcore_samples: bool,
261 #[serde(rename = "showStrainGraph")]
263 pub show_strain_graph: bool,
264 #[serde(rename = "showSliderBreaks")]
266 pub show_slider_breaks: bool,
267 #[serde(rename = "ignoreFail")]
269 pub ignore_fail: bool,
270}
271
272impl RenderOptions {
273 pub const DEFAULT_RESOLUTION: RenderResolution = RenderResolution::HD720;
274}
275
276impl Default for RenderOptions {
277 fn default() -> Self {
278 Self {
279 resolution: Self::DEFAULT_RESOLUTION,
280 global_volume: 50,
281 music_volume: 50,
282 hitsound_volume: 50,
283 show_hit_error_meter: true,
284 show_unstable_rate: true,
285 show_score: true,
286 show_hp_bar: true,
287 show_combo_counter: true,
288 show_pp_counter: true,
289 show_key_overlay: true,
290 show_scoreboard: true,
291 show_borders: true,
292 show_mods: true,
293 show_result_screen: true,
294 use_skin_cursor: true,
295 use_skin_colors: false,
296 use_skin_hitsounds: true,
297 use_beatmap_colors: true,
298 cursor_scale_to_cs: false,
299 cursor_rainbow: false,
300 cursor_trail_glow: false,
301 draw_follow_points: true,
302 draw_combo_numbers: true,
303 cursor_size: 1.0,
304 cursor_trail: true,
305 beat_scaling: false,
306 slider_merge: false,
307 objects_rainbow: false,
308 flash_objects: false,
309 use_slider_hitcircle_color: false,
310 seizure_warning: false,
311 load_storyboard: false,
312 load_video: false,
313 intro_bg_dim: 0,
314 ingame_bg_dim: 80,
315 break_bg_dim: 30,
316 bg_parallax: false,
317 show_danser_logo: true,
318 skip_intro: true,
319 cursor_ripples: false,
320 slider_snaking_in: true,
321 slider_snaking_out: true,
322 show_hit_counter: true,
323 show_avatars_on_scoreboard: false,
324 show_aim_error_meter: false,
325 play_nightcore_samples: true,
326 show_strain_graph: false,
327 show_slider_breaks: false,
328 ignore_fail: false,
329 }
330 }
331}
332
333#[derive(Clone, Debug, PartialEq, Eq)]
334pub enum RenderSkinOption<'a> {
335 Official { name: Cow<'a, str> },
336 Custom { id: u32 },
337}
338
339impl Default for RenderSkinOption<'_> {
340 fn default() -> Self {
341 Self::Official {
342 name: "default".into(),
343 }
344 }
345}
346
347impl From<u32> for RenderSkinOption<'_> {
348 fn from(id: u32) -> Self {
349 Self::Custom { id }
350 }
351}
352
353macro_rules! impl_from_name {
354 ( $( $ty:ty ),* ) => {
355 $(
356 impl<'a> From<$ty> for RenderSkinOption<'a> {
357 fn from(name: $ty) -> Self {
358 Self::Official { name: name.into() }
359 }
360 }
361 )*
362 };
363}
364
365impl_from_name!(&'a str, &'a String, String, Cow<'a, str>);
366
367impl<'de> Deserialize<'de> for RenderSkinOption<'static> {
368 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
369 struct SkinVisitor;
370
371 impl<'de> Visitor<'de> for SkinVisitor {
372 type Value = RenderSkinOption<'static>;
373
374 fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
375 f.write_str("`skin` and `customSkin` fields")
376 }
377
378 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
379 let mut custom_skin: Option<bool> = None;
380 let mut skin: Option<String> = None;
381
382 while let Some(key) = map.next_key()? {
383 match key {
384 "customSkin" => custom_skin = Some(map.next_value()?),
385 "skin" => skin = Some(map.next_value()?),
386 _ => {
387 let _: IgnoredAny = map.next_value()?;
388 }
389 }
390 }
391
392 let custom_skin =
393 custom_skin.ok_or_else(|| DeError::missing_field("customSkin"))?;
394 let skin = skin.ok_or_else(|| DeError::missing_field("skin"))?;
395
396 let skin = if custom_skin {
397 let id = skin
398 .parse()
399 .map_err(|_| DeError::invalid_value(Unexpected::Str(&skin), &"a u32"))?;
400
401 RenderSkinOption::Custom { id }
402 } else {
403 RenderSkinOption::Official {
404 name: Cow::Owned(skin),
405 }
406 };
407
408 Ok(skin)
409 }
410 }
411
412 d.deserialize_map(SkinVisitor)
413 }
414}
415
416#[derive(Clone, Debug, Deserialize, PartialEq)]
417pub struct RenderServers {
418 pub servers: Vec<RenderServer>,
419}
420
421impl Requestable for RenderServers {
422 fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
423 ClientError::response_error(bytes, status.as_u16())
424 }
425}
426
427#[derive(Clone, Debug, Deserialize, PartialEq)]
428pub struct RenderServer {
429 pub enabled: bool,
430 #[serde(rename = "lastSeen", deserialize_with = "deserialize_datetime")]
431 pub last_seen: OffsetDateTime,
432 pub name: Box<str>,
433 pub priority: f32,
434 #[serde(rename = "oldScore")]
435 pub old_score: f32,
436 #[serde(rename = "avgFPS")]
437 pub avg_fps: u32,
438 pub power: Box<str>,
439 pub status: Box<str>,
440 #[serde(rename = "totalRendered")]
441 pub total_rendered: u32,
442 #[serde(rename = "renderingType")]
443 pub rendering_type: Box<str>,
444 pub cpu: Box<str>,
445 pub gpu: Box<str>,
446 #[serde(rename = "motionBlurCapable")]
447 pub motion_blur_capable: bool,
448 #[serde(rename = "usingOsuApi")]
449 pub using_osu_api: bool,
450 #[serde(rename = "uhdCapable")]
451 pub uhd_capable: bool,
452 #[serde(rename = "avgRenderTime")]
453 pub avg_render_time: f32,
454 #[serde(rename = "avgUploadTime")]
455 pub avg_upload_time: f32,
456 #[serde(rename = "totalAvgTime")]
457 pub total_avg_time: f32,
458 #[serde(rename = "totalUploadedVideosSize")]
459 pub total_uploaded_videos_size: u32,
460 #[serde(rename = "ownerUserId")]
461 pub owner_user_id: u32,
462 #[serde(rename = "ownerUsername")]
463 pub owner_username: Box<str>,
464 pub customization: RenderServerOptions,
465}
466
467#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
468pub struct RenderServerOptions {
469 #[serde(rename = "textColor")]
470 pub text_color: Box<str>,
471 #[serde(rename = "backgroundType")]
472 pub background_type: i32,
473}
474
475#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
476pub struct ServerOnlineCount(pub u32);
477
478impl Requestable for ServerOnlineCount {
479 fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
480 ClientError::response_error(bytes, status.as_u16())
481 }
482}