mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 16:28:55 +00:00
Make OsuClient clone-able
This commit is contained in:
parent
177c714172
commit
8c34c7a3ba
5 changed files with 70 additions and 81 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
})?;
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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."),
|
||||
));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue