1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use serde::Deserialize;
5use typed_builder::TypedBuilder;
6
7use super::{de::*, GameMode, ModsFlag, UserId};
8
9#[derive(Debug, TypedBuilder)]
10#[builder(builder_type_doc = "Builder for creating request to get_beatmaps API,
11read https://github.com/ppy/osu-api/wiki#parameters for meaning")]
12pub struct GetBeatmapsProps<'u, 'k> {
13 api_key: &'k str,
14 #[builder(default = 0)]
15 beatmapset_id: u64,
16 #[builder(default = 0)]
17 beatmap_id: u64,
18 #[builder(default, setter(transform = |id: impl Into<UserId<'u>>| Some(id.into())))]
19 user_id: Option<UserId<'u>>,
20 #[builder(default, setter(strip_option))]
21 mode: Option<GameMode>,
22 #[builder(setter(strip_bool))]
23 include_converted: bool,
24 #[builder(default, setter(strip_option))]
25 beatmap_hash: Option<String>,
26 #[builder(default = 0)]
27 limit: u32,
28 #[builder(default = Vec::new())]
29 mods: Vec<ModsFlag>,
30 #[builder(default, setter(strip_option))]
31 since: Option<chrono::NaiveDate>,
32}
33
34impl<'u, 'k> TryFrom<GetBeatmapsProps<'u, 'k>> for HashMap<&'static str, String> {
35 type Error = ();
37
38 fn try_from(value: GetBeatmapsProps<'u, 'k>) -> std::result::Result<Self, Self::Error> {
39 let mut query = HashMap::new();
40
41 query.insert("k", value.api_key.to_string());
42
43 if value.beatmapset_id == 0 && value.beatmap_id == 0 {
44 return Err(());
45 }
46
47 if value.beatmapset_id != 0 {
48 query.insert("s", value.beatmapset_id.to_string());
49 }
50
51 if value.beatmap_id != 0 {
52 query.insert("b", value.beatmap_id.to_string());
53 }
54
55 if let Some(user_id) = value.user_id {
56 match user_id {
57 UserId::Id(id) => {
58 query.insert("u", id.to_string());
59 }
60 UserId::Username(name) => {
61 query.insert("u", name.to_string());
62 query.insert("type", "string".to_string());
63 }
64 };
65 }
66
67 if let Some(mode) = value.mode {
68 query.insert("m", mode.to_string());
69
70 let include_converted = if value.include_converted { "1" } else { "0" };
71 match mode {
72 GameMode::Standard => None, _ => query.insert("a", include_converted.to_string()),
74 };
75 }
76
77 if let Some(hash) = value.beatmap_hash {
78 query.insert("h", hash);
79 }
80
81 if value.limit != 0 {
82 query.insert("limit", value.limit.to_string());
83 }
84
85 if !value.mods.is_empty() {
86 let mods = value
87 .mods
88 .into_iter()
89 .fold(0_u64, |accum, item| accum | item.bits());
90
91 query.insert("mods", mods.to_string());
92 }
93
94 if let Some(date) = value.since {
95 query.insert("since", date.to_string());
96 }
97
98 Ok(query)
99 }
100}
101
102#[derive(Debug)]
103pub enum Approval {
104 Graveyard = -2,
105 WIP = -1,
106 Pending = 0,
107 Ranked = 1,
108 Approved = 2,
109 Qualified = 3,
110 Loved = 4,
111}
112
113#[derive(Deserialize)]
115pub struct GetBeatmapsResp {
116 #[serde(deserialize_with = "s_to_approval")]
118 pub approved: Approval,
119 #[serde(deserialize_with = "s_to_datetime")]
121 pub submit_date: DateTime<Utc>,
122 #[serde(deserialize_with = "s_to_datetime")]
124 pub approved_date: DateTime<Utc>,
125 #[serde(deserialize_with = "s_to_datetime")]
127 pub last_update: DateTime<Utc>,
128 pub artist: String,
129 #[serde(deserialize_with = "s_to_u64")]
131 pub beatmap_id: u64,
132 #[serde(deserialize_with = "s_to_u64")]
134 pub beatmapset_id: u64,
135 #[serde(deserialize_with = "s_to_u16")]
136 pub bpm: u16,
137 pub creator: String,
138 #[serde(deserialize_with = "s_to_u64")]
139 pub creator_id: u64,
140 #[serde(deserialize_with = "s_to_f64")]
142 pub difficultyrating: f64,
143 #[serde(deserialize_with = "s_to_f64")]
144 pub diff_aim: f64,
145 #[serde(deserialize_with = "s_to_f64")]
146 pub diff_speed: f64,
147 #[serde(deserialize_with = "s_to_f32")]
149 pub diff_size: f32,
150 #[serde(deserialize_with = "s_to_f32")]
152 pub diff_overall: f32,
153 #[serde(deserialize_with = "s_to_f32")]
155 pub diff_approach: f32,
156 #[serde(deserialize_with = "s_to_f32")]
158 pub diff_drain: f32,
159 #[serde(deserialize_with = "s_to_u64")]
161 pub hit_length: u64,
162 pub source: String,
163 pub genre_id: String,
164 pub language_id: String,
166 pub title: String,
168 pub total_length: String,
170 pub version: String,
172 pub file_md5: String,
173 pub mode: String,
176 pub tags: String,
178 pub favourite_count: String,
180 pub rating: String,
181 pub playcount: String,
183 pub passcount: String,
185 pub count_normal: String,
186 pub count_slider: String,
187 pub count_spinner: String,
188 pub max_combo: String,
190 #[serde(deserialize_with = "s_to_bool")]
192 pub storyboard: bool,
193 #[serde(deserialize_with = "s_to_bool")]
195 pub video: bool,
196 #[serde(deserialize_with = "s_to_bool")]
198 pub download_unavailable: bool,
199 #[serde(deserialize_with = "s_to_bool")]
201 pub audio_unavailable: bool,
202}