osu_api/api_v1/models/
beatmaps.rs

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  /// There is only one error represent that the beatmapset_id and beatmap_id are both not given
36  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, // do nothing
73        _ => 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/// TODO: convert all type
114#[derive(Deserialize)]
115pub struct GetBeatmapsResp {
116  // 4 = loved, 3 = qualified, 2 = approved, 1 = ranked, 0 = pending, -1 = WIP, -2 = graveyard
117  #[serde(deserialize_with = "s_to_approval")]
118  pub approved: Approval,
119  // date submitted, in UTC
120  #[serde(deserialize_with = "s_to_datetime")]
121  pub submit_date: DateTime<Utc>,
122  // date ranked, in UTC
123  #[serde(deserialize_with = "s_to_datetime")]
124  pub approved_date: DateTime<Utc>,
125  // last update date, in UTC. May be after approved_date if map was unranked and reranked.
126  #[serde(deserialize_with = "s_to_datetime")]
127  pub last_update: DateTime<Utc>,
128  pub artist: String,
129  // beatmap_id is per difficulty
130  #[serde(deserialize_with = "s_to_u64")]
131  pub beatmap_id: u64,
132  // beatmapset_id groups difficulties into a set
133  #[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  // The number of stars the map would have in-game and on the website
141  #[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  // Circle size value (CS)
148  #[serde(deserialize_with = "s_to_f32")]
149  pub diff_size: f32,
150  // Overall difficulty (OD)
151  #[serde(deserialize_with = "s_to_f32")]
152  pub diff_overall: f32,
153  // Approach Rate (AR)
154  #[serde(deserialize_with = "s_to_f32")]
155  pub diff_approach: f32,
156  // Health drain (HP)
157  #[serde(deserialize_with = "s_to_f32")]
158  pub diff_drain: f32,
159  // seconds from first note to last note not including breaks
160  #[serde(deserialize_with = "s_to_u64")]
161  pub hit_length: u64,
162  pub source: String,
163  pub genre_id: String,
164  // 0 = any, 1 = unspecified, 2 = english, 3 = japanese, 4 = chinese, 5 = instrumental, 6 = korean, 7 = french, 8 = german, 9 = swedish, 10 = spanish, 11 = italian, 12 = russian, 13 = polish, 14 = other
165  pub language_id: String,
166  // song name
167  pub title: String,
168  // seconds from first note to last note including breaks
169  pub total_length: String,
170  // difficulty name
171  pub version: String,
172  pub file_md5: String,
173  // md5 hash of the beatmap
174  // game mode,
175  pub mode: String,
176  // Beatmap tags separated by spaces.
177  pub tags: String,
178  // Number of times the beatmap was favourited. (Americans: notice the ou!)
179  pub favourite_count: String,
180  pub rating: String,
181  // Number of times the beatmap was played
182  pub playcount: String,
183  // Number of times the beatmap was passed, completed (the user didn't fail or retry)
184  pub passcount: String,
185  pub count_normal: String,
186  pub count_slider: String,
187  pub count_spinner: String,
188  // The maximum combo a user can reach playing this beatmap.
189  pub max_combo: String,
190  // If this beatmap has a storyboard
191  #[serde(deserialize_with = "s_to_bool")]
192  pub storyboard: bool,
193  // If this beatmap has a video
194  #[serde(deserialize_with = "s_to_bool")]
195  pub video: bool,
196  // If the download for this beatmap is unavailable (old map, etc.)
197  #[serde(deserialize_with = "s_to_bool")]
198  pub download_unavailable: bool,
199  // If the audio for this beatmap is unavailable (DMCA takedown, etc.)
200  #[serde(deserialize_with = "s_to_bool")]
201  pub audio_unavailable: bool,
202}