From 8c34c7a3ba7b9c56c9ce541535f12198dcffac8a Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sun, 2 Feb 2020 15:47:21 -0500 Subject: [PATCH] Make OsuClient clone-able --- youmubot-osu/src/lib.rs | 36 +++++++------- youmubot/src/commands/osu/announcer.rs | 33 +++++++------ youmubot/src/commands/osu/hook.rs | 11 ++--- youmubot/src/commands/osu/mod.rs | 67 +++++++++++--------------- youmubot/src/main.rs | 4 +- 5 files changed, 70 insertions(+), 81 deletions(-) diff --git a/youmubot-osu/src/lib.rs b/youmubot-osu/src/lib.rs index 38c76b0..fa62543 100644 --- a/youmubot-osu/src/lib.rs +++ b/youmubot-osu/src/lib.rs @@ -10,11 +10,14 @@ use request::builders::*; use request::*; use reqwest::blocking::{Client as HTTPClient, RequestBuilder, Response}; use serenity::framework::standard::CommandError as Error; -use std::convert::TryInto; +use std::{convert::TryInto, sync::Arc}; /// Client is the client that will perform calls to the osu! api server. +/// It's cheap to clone, so do it. +#[derive(Clone)] pub struct Client { - key: String, + key: Arc, + client: HTTPClient, } fn vec_try_into>(v: Vec) -> Result, T::Error> { @@ -29,52 +32,50 @@ fn vec_try_into>(v: Vec) -> Result, T:: impl Client { /// Create a new client from the given API key. - pub fn new(key: impl AsRef) -> Client { + pub fn new(http_client: HTTPClient, key: String) -> Client { Client { - key: key.as_ref().to_string(), + key: Arc::new(key), + client: http_client, } } - fn build_request(&self, c: &HTTPClient, r: RequestBuilder) -> Result { - let v = r.query(&[("k", &self.key)]).build()?; + fn build_request(&self, r: RequestBuilder) -> Result { + let v = r.query(&[("k", &*self.key)]).build()?; dbg!(v.url()); - Ok(c.execute(v)?) + Ok(self.client.execute(v)?) } pub fn beatmaps( &self, - client: &HTTPClient, kind: BeatmapRequestKind, f: impl FnOnce(&mut BeatmapRequestBuilder) -> &mut BeatmapRequestBuilder, ) -> Result, Error> { let mut r = BeatmapRequestBuilder::new(kind); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; Ok(vec_try_into(res)?) } pub fn user( &self, - client: &HTTPClient, user: UserID, f: impl FnOnce(&mut UserRequestBuilder) -> &mut UserRequestBuilder, ) -> Result, Error> { let mut r = UserRequestBuilder::new(user); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let res = vec_try_into(res)?; Ok(res.into_iter().next()) } pub fn scores( &self, - client: &HTTPClient, beatmap_id: u64, f: impl FnOnce(&mut ScoreRequestBuilder) -> &mut ScoreRequestBuilder, ) -> Result, Error> { let mut r = ScoreRequestBuilder::new(beatmap_id); f(&mut r); - let res: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let mut res: Vec = vec_try_into(res)?; // with a scores request you need to fill the beatmap ids yourself @@ -86,32 +87,29 @@ impl Client { 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) + self.user_scores(UserScoreType::Best, 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) + self.user_scores(UserScoreType::Recent, 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: Vec = self.build_request(client, r.build(client))?.json()?; + let res: Vec = self.build_request(r.build(&self.client))?.json()?; let res = vec_try_into(res)?; Ok(res) } diff --git a/youmubot/src/commands/osu/announcer.rs b/youmubot/src/commands/osu/announcer.rs index 60a8166..fa08b3f 100644 --- a/youmubot/src/commands/osu/announcer.rs +++ b/youmubot/src/commands/osu/announcer.rs @@ -33,8 +33,7 @@ impl Announcer for OsuAnnouncer { d: &mut ShareMap, channels: impl Fn(UserId) -> Vec + Sync, ) -> CommandResult { - let http = d.get::().expect("HTTP"); - let osu = d.get::().expect("osu!client"); + let osu = d.get::().expect("osu!client").clone(); // For each user... let mut data = d .get::() @@ -43,20 +42,27 @@ impl Announcer for OsuAnnouncer { for (user_id, osu_user) in data.iter_mut() { let mut user = None; for mode in &[Mode::Std, Mode::Taiko, Mode::Mania, Mode::Catch] { - let scores = OsuAnnouncer::scan_user(http, osu, osu_user, *mode)?; + let scores = OsuAnnouncer::scan_user(&osu, osu_user, *mode)?; if scores.is_empty() { continue; } - let user = user.get_or_insert_with(|| { - osu.user(http, UserID::ID(osu_user.id), |f| f) - .unwrap() - .unwrap() - }); + let user = { + let user = &mut user; + if let None = user { + match osu.user(UserID::ID(osu_user.id), |f| f.mode(*mode)) { + Ok(u) => { + *user = u; + } + Err(_) => continue, + } + }; + user.as_ref().unwrap() + }; scores .into_par_iter() .filter_map(|(rank, score)| { let beatmap = osu - .beatmaps(http, BeatmapRequestKind::Beatmap(score.beatmap_id), |f| f) + .beatmaps(BeatmapRequestKind::Beatmap(score.beatmap_id), |f| f) .map(|v| BeatmapWithMode(v.into_iter().next().unwrap(), *mode)); let channels = channels(*user_id); match beatmap { @@ -89,13 +95,8 @@ impl Announcer for OsuAnnouncer { } impl OsuAnnouncer { - fn scan_user( - http: &HTTPClient, - osu: &OsuClient, - u: &OsuUser, - mode: Mode, - ) -> Result, Error> { - let scores = osu.user_best(http, UserID::ID(u.id), |f| f.mode(mode).limit(25))?; + fn scan_user(osu: &OsuClient, u: &OsuUser, mode: Mode) -> Result, Error> { + let scores = osu.user_best(UserID::ID(u.id), |f| f.mode(mode).limit(25))?; let scores = scores .into_iter() .filter(|s: &Score| s.date >= u.last_update) diff --git a/youmubot/src/commands/osu/hook.rs b/youmubot/src/commands/osu/hook.rs index b109262..3c1adbc 100644 --- a/youmubot/src/commands/osu/hook.rs +++ b/youmubot/src/commands/osu/hook.rs @@ -71,9 +71,7 @@ struct ToPrint<'a> { } fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result>, Error> { - let data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = ctx.data.read().get::().unwrap().clone(); let mut to_prints: Vec> = Vec::new(); for capture in OLD_LINK_REGEX.captures_iter(content) { let req_type = capture.name("link_type").unwrap().as_str(); @@ -95,7 +93,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result return None, }) }); - let beatmaps = osu.beatmaps(reqwest, req, |v| match mode { + let beatmaps = osu.beatmaps(req, |v| match mode { Some(m) => v.mode(m, true), None => v, })?; @@ -124,8 +122,7 @@ fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result(ctx: &mut Context, content: &'a str) -> Result>, Error> { let data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = data.get::().unwrap().clone(); let mut to_prints: Vec> = Vec::new(); for capture in NEW_LINK_REGEX.captures_iter(content) { let mode = capture.name("mode").and_then(|v| { @@ -145,7 +142,7 @@ fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result v.mode(m, true), None => v, })?; diff --git a/youmubot/src/commands/osu/mod.rs b/youmubot/src/commands/osu/mod.rs index 24a11af..2e8fac2 100644 --- a/youmubot/src/commands/osu/mod.rs +++ b/youmubot/src/commands/osu/mod.rs @@ -91,15 +91,14 @@ impl AsRef for BeatmapWithMode { #[usage = "[username or user_id]"] #[num_args(1)] pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let mut data = ctx.data.write(); - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = ctx.data.read().get::().unwrap().clone(); let user = args.single::()?; - let user: Option = osu.user(reqwest, UserID::Auto(user), |f| f)?; + let user: Option = osu.user(UserID::Auto(user), |f| f)?; match user { Some(u) => { - let mut db: DBWriteGuard<_> = data + let mut db = ctx.data.write(); + let mut db: DBWriteGuard<_> = db .get_mut::() .ok_or(Error::from("DB uninitialized"))? .into(); @@ -201,28 +200,27 @@ impl FromStr for Nth { #[example = "#1 / taiko / natsukagami"] #[max_args(3)] pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { - let mut data = ctx.data.write(); - let nth = args.single::().unwrap_or(Nth(1)).0.min(50).max(1); let mode = args.single::().unwrap_or(ModeArg(Mode::Std)).0; - let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; + let user = UsernameArg::to_user_id_query( + args.single::().ok(), + &mut *ctx.data.write(), + msg, + )?; - let reqwest = data.get::().unwrap(); - let osu: &OsuClient = data.get::().unwrap(); + let osu: OsuClient = ctx.data.read().get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f.mode(mode))? + .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; let recent_play = osu - .user_recent(reqwest, UserID::ID(user.id), |f| f.mode(mode).limit(nth))? + .user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))? .into_iter() .last() .ok_or(Error::from("No such play"))?; let beatmap = osu - .beatmaps( - reqwest, - BeatmapRequestKind::Beatmap(recent_play.beatmap_id), - |f| f.mode(mode, true), - )? + .beatmaps(BeatmapRequestKind::Beatmap(recent_play.beatmap_id), |f| { + f.mode(mode, true) + })? .into_iter() .next() .map(|v| BeatmapWithMode(v, mode)) @@ -237,7 +235,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult })?; // Save the beatmap... - cache::save_beatmap(&mut *data, msg.channel_id, &beatmap)?; + cache::save_beatmap(&mut *ctx.data.write(), msg.channel_id, &beatmap)?; Ok(()) } @@ -287,15 +285,12 @@ pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); + let osu = data.get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f)? + .user(user, |f| f)? .ok_or(Error::from("User not found"))?; - let scores = osu.scores(reqwest, b.beatmap_id, |f| { - f.user(UserID::ID(user.id)).mode(m) - })?; + let scores = osu.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))?; if scores.is_empty() { msg.reply(&ctx, "No scores found")?; @@ -327,12 +322,11 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { let mut data = ctx.data.write(); let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu: &OsuClient = data.get::().unwrap(); + let osu: OsuClient = data.get::().unwrap().clone(); let user = osu - .user(reqwest, user, |f| f.mode(mode))? + .user(user, |f| f.mode(mode))? .ok_or(Error::from("User not found"))?; - let top_play = osu.user_best(reqwest, UserID::ID(user.id), |f| f.mode(mode).limit(nth))?; + let top_play = osu.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(nth))?; let rank = top_play.len() as u8; @@ -341,11 +335,9 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { .last() .ok_or(Error::from("No such play"))?; let beatmap = osu - .beatmaps( - reqwest, - BeatmapRequestKind::Beatmap(top_play.beatmap_id), - |f| f.mode(mode, true), - )? + .beatmaps(BeatmapRequestKind::Beatmap(top_play.beatmap_id), |f| { + f.mode(mode, true) + })? .into_iter() .next() .map(|v| BeatmapWithMode(v, mode)) @@ -368,17 +360,16 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { fn get_user(ctx: &mut Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult { let mut data = ctx.data.write(); let user = UsernameArg::to_user_id_query(args.single::().ok(), &mut *data, msg)?; - let reqwest = data.get::().unwrap(); - let osu = data.get::().unwrap(); - let user = osu.user(reqwest, user, |f| f.mode(mode))?; + let osu = data.get::().unwrap().clone(); + let user = osu.user(user, |f| f.mode(mode))?; match user { Some(u) => { let best = osu - .user_best(reqwest, UserID::ID(u.id), |f| f.limit(1).mode(mode))? + .user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))? .into_iter() .next() .map(|m| { - osu.beatmaps(reqwest, BeatmapRequestKind::Beatmap(m.beatmap_id), |f| { + osu.beatmaps(BeatmapRequestKind::Beatmap(m.beatmap_id), |f| { f.mode(mode, true) }) .map(|map| (m, BeatmapWithMode(map.into_iter().next().unwrap(), mode))) diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index ae23ca4..8b5b504 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -48,8 +48,10 @@ fn main() { // Setup shared instances of things { let mut data = client.data.write(); - data.insert::(reqwest::blocking::Client::new()); + let http_client = reqwest::blocking::Client::new(); + data.insert::(http_client.clone()); data.insert::(OsuClient::new( + http_client.clone(), var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."), )); }