Make OsuClient clone-able

This commit is contained in:
Natsu Kagami 2020-02-02 15:47:21 -05:00
parent 177c714172
commit 8c34c7a3ba
5 changed files with 70 additions and 81 deletions

View file

@ -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<String>,
client: HTTPClient,
}
fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
@ -29,52 +32,50 @@ fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::
impl Client {
/// Create a new client from the given API key.
pub fn new(key: impl AsRef<str>) -> 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<Response, Error> {
let v = r.query(&[("k", &self.key)]).build()?;
fn build_request(&self, r: RequestBuilder) -> Result<Response, Error> {
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<Vec<Beatmap>, Error> {
let mut r = BeatmapRequestBuilder::new(kind);
f(&mut r);
let res: Vec<raw::Beatmap> = self.build_request(client, r.build(client))?.json()?;
let res: Vec<raw::Beatmap> = 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<Option<User>, Error> {
let mut r = UserRequestBuilder::new(user);
f(&mut r);
let res: Vec<raw::User> = self.build_request(client, r.build(client))?.json()?;
let res: Vec<raw::User> = 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<Vec<Score>, Error> {
let mut r = ScoreRequestBuilder::new(beatmap_id);
f(&mut r);
let res: Vec<raw::Score> = self.build_request(client, r.build(client))?.json()?;
let res: Vec<raw::Score> = self.build_request(r.build(&self.client))?.json()?;
let mut res: Vec<Score> = 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<Vec<Score>, 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<Vec<Score>, 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<Vec<Score>, Error> {
let mut r = UserScoreRequestBuilder::new(u, user);
f(&mut r);
let res: Vec<raw::Score> = self.build_request(client, r.build(client))?.json()?;
let res: Vec<raw::Score> = self.build_request(r.build(&self.client))?.json()?;
let res = vec_try_into(res)?;
Ok(res)
}

View file

@ -33,8 +33,7 @@ impl Announcer for OsuAnnouncer {
d: &mut ShareMap,
channels: impl Fn(UserId) -> Vec<ChannelId> + Sync,
) -> CommandResult {
let http = d.get::<HTTP>().expect("HTTP");
let osu = d.get::<Osu>().expect("osu!client");
let osu = d.get::<Osu>().expect("osu!client").clone();
// For each user...
let mut data = d
.get::<OsuSavedUsers>()
@ -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<Vec<(u8, Score)>, 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<Vec<(u8, Score)>, 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)

View file

@ -71,9 +71,7 @@ struct ToPrint<'a> {
}
fn handle_old_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPrint<'a>>, Error> {
let data = ctx.data.write();
let reqwest = data.get::<http::HTTP>().unwrap();
let osu = data.get::<http::Osu>().unwrap();
let osu = ctx.data.read().get::<http::Osu>().unwrap().clone();
let mut to_prints: Vec<ToPrint<'a>> = 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<Vec<ToPri
_ => 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<Vec<ToPri
fn handle_new_links<'a>(ctx: &mut Context, content: &'a str) -> Result<Vec<ToPrint<'a>>, Error> {
let data = ctx.data.write();
let reqwest = data.get::<http::HTTP>().unwrap();
let osu = data.get::<http::Osu>().unwrap();
let osu = data.get::<http::Osu>().unwrap().clone();
let mut to_prints: Vec<ToPrint<'a>> = 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<Vec<ToPri
BeatmapRequestKind::Beatmapset(capture.name("set_id").unwrap().as_str().parse()?)
}
};
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,
})?;

View file

@ -91,15 +91,14 @@ impl AsRef<Beatmap> 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::<http::HTTP>().unwrap();
let osu = data.get::<http::Osu>().unwrap();
let osu = ctx.data.read().get::<http::Osu>().unwrap().clone();
let user = args.single::<String>()?;
let user: Option<User> = osu.user(reqwest, UserID::Auto(user), |f| f)?;
let user: Option<User> = 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::<OsuSavedUsers>()
.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::<Nth>().unwrap_or(Nth(1)).0.min(50).max(1);
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
let user = UsernameArg::to_user_id_query(args.single::<UsernameArg>().ok(), &mut *data, msg)?;
let user = UsernameArg::to_user_id_query(
args.single::<UsernameArg>().ok(),
&mut *ctx.data.write(),
msg,
)?;
let reqwest = data.get::<http::HTTP>().unwrap();
let osu: &OsuClient = data.get::<http::Osu>().unwrap();
let osu: OsuClient = ctx.data.read().get::<http::Osu>().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::<UsernameArg>().ok(), &mut *data, msg)?;
let reqwest = data.get::<http::HTTP>().unwrap();
let osu = data.get::<http::Osu>().unwrap();
let osu = data.get::<http::Osu>().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::<UsernameArg>().ok(), &mut *data, msg)?;
let reqwest = data.get::<http::HTTP>().unwrap();
let osu: &OsuClient = data.get::<http::Osu>().unwrap();
let osu: OsuClient = data.get::<http::Osu>().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::<UsernameArg>().ok(), &mut *data, msg)?;
let reqwest = data.get::<http::HTTP>().unwrap();
let osu = data.get::<http::Osu>().unwrap();
let user = osu.user(reqwest, user, |f| f.mode(mode))?;
let osu = data.get::<http::Osu>().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)))

View file

@ -48,8 +48,10 @@ fn main() {
// Setup shared instances of things
{
let mut data = client.data.write();
data.insert::<http::HTTP>(reqwest::blocking::Client::new());
let http_client = reqwest::blocking::Client::new();
data.insert::<http::HTTP>(http_client.clone());
data.insert::<http::Osu>(OsuClient::new(
http_client.clone(),
var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."),
));
}