diff --git a/youmubot-osu/src/lib.rs b/youmubot-osu/src/lib.rs index f58948b..5c15bf3 100644 --- a/youmubot-osu/src/lib.rs +++ b/youmubot-osu/src/lib.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; use std::convert::TryInto; use std::sync::Arc; +use futures_util::lock::Mutex; use models::*; use request::builders::*; use request::*; @@ -14,6 +16,8 @@ pub mod request; #[derive(Clone)] pub struct Client { rosu: Arc, + + user_header_cache: Arc>>>, } pub fn vec_try_into>(v: Vec) -> Result, T::Error> { @@ -36,6 +40,7 @@ impl Client { .await?; Ok(Client { rosu: Arc::new(rosu), + user_header_cache: Arc::new(Mutex::new(HashMap::new())), }) } @@ -54,9 +59,26 @@ impl Client { user: UserID, f: impl FnOnce(&mut UserRequestBuilder) -> &mut UserRequestBuilder, ) -> Result, Error> { - let mut r = UserRequestBuilder::new(user); + let mut r = UserRequestBuilder::new(user.clone()); f(&mut r); - r.build(self).await + let u = r.build(self).await?; + if let UserID::ID(id) = user { + self.user_header_cache + .lock() + .await + .insert(id, u.clone().map(|v| v.into())); + } + Ok(u) + } + + /// Fetch the user header. + pub async fn user_header(&self, id: u64) -> Result, Error> { + Ok( + match self.user_header_cache.lock().await.get(&id).cloned() { + Some(v) => v, + None => self.user(UserID::ID(id), |f| f).await?.map(|v| v.into()), + }, + ) } pub async fn scores( diff --git a/youmubot-osu/src/models/mod.rs b/youmubot-osu/src/models/mod.rs index 7bdbfe1..b224c2c 100644 --- a/youmubot-osu/src/models/mod.rs +++ b/youmubot-osu/src/models/mod.rs @@ -460,6 +460,13 @@ impl UserEvent { } } +#[derive(Clone, Debug)] +pub struct UserHeader { + pub id: u64, + pub username: String, + pub country: String, +} + #[derive(Clone, Debug)] pub struct User { pub id: u64, @@ -498,6 +505,36 @@ impl User { } } +impl UserHeader { + pub fn link(&self) -> String { + format!("https://osu.ppy.sh/users/{}", self.id) + } + + pub fn avatar_url(&self) -> String { + format!("https://a.ppy.sh/{}", self.id) + } +} + +impl<'a> From<&'a User> for UserHeader { + fn from(u: &'a User) -> Self { + Self { + id: u.id, + username: u.username.clone(), + country: u.country.clone(), + } + } +} + +impl From for UserHeader { + fn from(u: User) -> Self { + Self { + id: u.id, + username: u.username, + country: u.country, + } + } +} + #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum Rank { SS, @@ -547,6 +584,7 @@ pub struct Score { pub normalized_score: u32, pub pp: Option, pub rank: Rank, + pub mode: Mode, pub mods: Mods, // Later pub count_300: u64, diff --git a/youmubot-osu/src/models/rosu.rs b/youmubot-osu/src/models/rosu.rs index 75f138e..ca339e7 100644 --- a/youmubot-osu/src/models/rosu.rs +++ b/youmubot-osu/src/models/rosu.rs @@ -127,6 +127,7 @@ impl From for Score { server_accuracy: s.accuracy as f64, global_rank: s.rank_global, effective_pp: s.weight.map(|w| w.pp as f64), + mode: s.mode.into(), mods: s .mods .iter() diff --git a/youmubot-osu/src/request.rs b/youmubot-osu/src/request.rs index 6d68cba..42d7d09 100644 --- a/youmubot-osu/src/request.rs +++ b/youmubot-osu/src/request.rs @@ -1,55 +1,9 @@ use crate::models::{Mode, Mods}; use crate::Client; -use chrono::{DateTime, Utc}; use rosu_v2::error::OsuError; use youmubot_prelude::*; -trait ToQuery { - fn to_query(&self) -> Vec<(&'static str, String)>; -} - -impl ToQuery for Option { - fn to_query(&self) -> Vec<(&'static str, String)> { - match self { - Some(ref v) => v.to_query(), - None => vec![], - } - } -} - -impl ToQuery for Mods { - fn to_query(&self) -> Vec<(&'static str, String)> { - vec![("mods", format!("{}", self.bits()))] - } -} - -impl ToQuery for Mode { - fn to_query(&self) -> Vec<(&'static str, String)> { - vec![("m", (*self as u8).to_string())] - } -} - -impl ToQuery for (Mode, bool) { - fn to_query(&self) -> Vec<(&'static str, String)> { - vec![ - ("m", (self.0 as u8).to_string()), - ("a", (self.1 as u8).to_string()), - ] - } -} - -impl ToQuery for (&'static str, String) { - fn to_query(&self) -> Vec<(&'static str, String)> { - vec![(self.0, self.1.clone())] - } -} - -impl ToQuery for (&'static str, DateTime) { - fn to_query(&self) -> Vec<(&'static str, String)> { - vec![(self.0, format!("{}", self.1.format("%Y-%m-%d")))] - } -} - +#[derive(Clone, Debug)] pub enum UserID { Username(String), ID(u64), @@ -74,32 +28,12 @@ impl UserID { } } -impl ToQuery for UserID { - fn to_query(&self) -> Vec<(&'static str, String)> { - use UserID::*; - match self { - Username(ref s) => vec![("u", s.clone()), ("type", "string".to_owned())], - ID(u) => vec![("u", u.to_string()), ("type", "id".to_owned())], - } - } -} pub enum BeatmapRequestKind { Beatmap(u64), Beatmapset(u64), BeatmapHash(String), } -impl ToQuery for BeatmapRequestKind { - fn to_query(&self) -> Vec<(&'static str, String)> { - use BeatmapRequestKind::*; - match self { - Beatmap(b) => vec![("b", b.to_string())], - Beatmapset(s) => vec![("s", s.to_string())], - BeatmapHash(ref h) => vec![("h", h.clone())], - } - } -} - fn handle_not_found(v: Result) -> Result, OsuError> { match v { Ok(v) => Ok(Some(v)),