diff --git a/youmubot-osu/src/lib.rs b/youmubot-osu/src/lib.rs index 68548fb..7e944e4 100644 --- a/youmubot-osu/src/lib.rs +++ b/youmubot-osu/src/lib.rs @@ -24,6 +24,16 @@ impl Client { } } + fn build_request( + &self, + c: &HTTPClient, + r: reqwest::RequestBuilder, + ) -> Result { + let v = r.query(&[("k", &self.key)]).build()?; + // println!("{}", v.url()); + Ok(c.execute(v)?) + } + pub fn beatmaps( &self, client: &HTTPClient, @@ -32,7 +42,7 @@ impl Client { ) -> Result, Error> { let mut r = BeatmapRequestBuilder::new(kind); f(&mut r); - let res = r.build(client).query(&[("k", &self.key)]).send()?.json()?; + let res = self.build_request(client, r.build(client))?.json()?; Ok(res) } @@ -44,7 +54,7 @@ impl Client { ) -> Result, Error> { let mut r = UserRequestBuilder::new(user); f(&mut r); - let res: Vec<_> = r.build(client).query(&[("k", &self.key)]).send()?.json()?; + let res: Vec<_> = self.build_request(client, r.build(client))?.json()?; Ok(res.into_iter().next()) } @@ -56,7 +66,43 @@ impl Client { ) -> Result, Error> { let mut r = ScoreRequestBuilder::new(beatmap_id); f(&mut r); - let res = r.build(client).query(&[("k", &self.key)]).send()?.json()?; + let mut res: Vec = self.build_request(client, r.build(client))?.json()?; + + // with a scores request you need to fill the beatmap ids yourself + res.iter_mut().for_each(|v| { + v.beatmap_id = beatmap_id; + }); + Ok(res) + } + + pub fn user_best( + &self, + client: &HTTPClient, + user: UserID, + f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, + ) -> Result, Error> { + self.user_scores(UserScoreType::Best, client, user, f) + } + + pub fn user_recent( + &self, + client: &HTTPClient, + user: UserID, + f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, + ) -> Result, Error> { + self.user_scores(UserScoreType::Recent, client, user, f) + } + + fn user_scores( + &self, + u: UserScoreType, + client: &HTTPClient, + user: UserID, + f: impl FnOnce(&mut UserScoreRequestBuilder) -> &mut UserScoreRequestBuilder, + ) -> Result, Error> { + let mut r = UserScoreRequestBuilder::new(u, user); + f(&mut r); + let res = self.build_request(client, r.build(client))?.json()?; Ok(res) } } diff --git a/youmubot-osu/src/models/deser.rs b/youmubot-osu/src/models/deser.rs index 345180f..1f5808c 100644 --- a/youmubot-osu/src/models/deser.rs +++ b/youmubot-osu/src/models/deser.rs @@ -13,15 +13,18 @@ impl<'de> Deserialize<'de> for Score { { let raw: raw::Score = raw::Score::deserialize(deserializer)?; Ok(Score { - id: parse_from_str(&raw.score_id)?, - username: raw.username, + id: raw.score_id.map(parse_from_str).transpose()?, user_id: parse_from_str(&raw.user_id)?, date: parse_date(&raw.date)?, + beatmap_id: raw.beatmap_id.map(parse_from_str).transpose()?.unwrap_or(0), replay_available: parse_bool(&raw.replay_available)?, score: parse_from_str(&raw.score)?, pp: parse_from_str(&raw.pp)?, rank: parse_from_str(&raw.rank)?, - mods: parse_from_str(&raw.enabled_mods)?, + mods: { + let v: u64 = parse_from_str(&raw.enabled_mods)?; + Mods::from_bits(v).unwrap_or(Mods::NOMOD) + }, count_300: parse_from_str(&raw.count300)?, count_100: parse_from_str(&raw.count100)?, count_50: parse_from_str(&raw.count50)?, diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index f53d225..0e7ddf2 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -149,6 +149,18 @@ pub struct Beatmap { pub pass_count: u64, } +const NEW_MODE_NAMES: [&'static str; 4] = ["osu", "taiko", "fruits", "mania"]; + +impl Beatmap { + /// Gets a link pointing to the beatmap, in the new format. + pub fn link(&self) -> String { + format!( + "https://osu.ppy.sh/beatmapsets/{}#{}/{}", + self.beatmapset_id, NEW_MODE_NAMES[self.mode as usize], self.beatmap_id + ) + } +} + #[derive(Debug)] pub struct UserEvent { pub display_html: String, @@ -203,8 +215,8 @@ impl std::str::FromStr for Rank { type Err = String; fn from_str(a: &str) -> Result { Ok(match &a.to_uppercase()[..] { - "SS" => Rank::SS, - "SSH" => Rank::SSH, + "SS" | "X" => Rank::SS, + "SSH" | "XH" => Rank::SSH, "S" => Rank::S, "SH" => Rank::SH, "A" => Rank::A, @@ -225,11 +237,11 @@ impl fmt::Display for Rank { #[derive(Debug)] pub struct Score { - pub id: u64, - pub username: String, + pub id: Option, // No id if you fail pub user_id: u64, pub date: DateTime, pub replay_available: bool, + pub beatmap_id: u64, pub score: u64, pub pp: f64, diff --git a/youmubot-osu/src/models/raw.rs b/youmubot-osu/src/models/raw.rs index ba70742..6d90fa5 100644 --- a/youmubot-osu/src/models/raw.rs +++ b/youmubot-osu/src/models/raw.rs @@ -78,9 +78,9 @@ pub(crate) struct UserEvent { #[derive(Deserialize, Debug)] pub(crate) struct Score { - pub score_id: String, + pub score_id: Option, + pub beatmap_id: Option, pub score: String, - pub username: String, pub count300: String, pub count100: String, pub count50: String, diff --git a/youmubot-osu/src/request.rs b/youmubot-osu/src/request.rs index eac31be..6db8614 100644 --- a/youmubot-osu/src/request.rs +++ b/youmubot-osu/src/request.rs @@ -207,6 +207,50 @@ pub mod builders { .query(&self.limit.map(|v| ("limit", v.to_string())).to_query()) } } + + pub(crate) enum UserScoreType { + Recent, + Best, + } + + pub struct UserScoreRequestBuilder { + score_type: UserScoreType, + user: UserID, + mode: Option, + limit: Option, + } + + impl UserScoreRequestBuilder { + pub(crate) fn new(score_type: UserScoreType, user: UserID) -> Self { + UserScoreRequestBuilder { + score_type, + user, + mode: None, + limit: None, + } + } + + pub fn mode(&mut self, m: Mode) -> &mut Self { + self.mode = Some(m); + self + } + + pub fn limit(&mut self, limit: u8) -> &mut Self { + self.limit = Some(limit).filter(|&v| v <= 100).or(self.limit); + self + } + + pub(crate) fn build(&self, client: &Client) -> RequestBuilder { + client + .get(match self.score_type { + UserScoreType::Best => "https://osu.ppy.sh/api/get_user_best", + UserScoreType::Recent => "https://osu.ppy.sh/api/get_user_recent" + }) + .query(&self.user.to_query()) + .query(&self.mode.to_query()) + .query(&self.limit.map(|v| ("limit", v.to_string())).to_query()) + } + } } pub struct UserBestRequest {