mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
osu: Fix commands
This commit is contained in:
parent
421cfd46af
commit
b776a1d26e
3 changed files with 376 additions and 303 deletions
|
@ -5,7 +5,6 @@ use crate::{
|
|||
request::{BeatmapRequestKind, UserID},
|
||||
Client as OsuHttpClient,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use serenity::{
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
|
@ -14,7 +13,7 @@ use serenity::{
|
|||
model::channel::Message,
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
mod announcer;
|
||||
|
@ -36,7 +35,7 @@ use server_rank::SERVER_RANK_COMMAND;
|
|||
pub(crate) struct OsuClient;
|
||||
|
||||
impl TypeMapKey for OsuClient {
|
||||
type Value = OsuHttpClient;
|
||||
type Value = Arc<OsuHttpClient>;
|
||||
}
|
||||
|
||||
/// Sets up the osu! command handling section.
|
||||
|
@ -50,9 +49,9 @@ impl TypeMapKey for OsuClient {
|
|||
/// - Commands on the "osu" prefix
|
||||
/// - Hooks. Hooks are completely opt-in.
|
||||
///
|
||||
pub fn setup(
|
||||
pub async fn setup(
|
||||
path: &std::path::Path,
|
||||
data: &mut ShareMap,
|
||||
data: &mut TypeMap,
|
||||
announcers: &mut AnnouncerHandler,
|
||||
) -> CommandResult {
|
||||
// Databases
|
||||
|
@ -60,11 +59,10 @@ pub fn setup(
|
|||
OsuLastBeatmap::insert_into(&mut *data, &path.join("last_beatmaps.yaml"))?;
|
||||
|
||||
// API client
|
||||
let http_client = data.get_cloned::<HTTPClient>();
|
||||
let osu_client = OsuHttpClient::new(
|
||||
http_client.clone(),
|
||||
let http_client = data.get::<HTTPClient>().unwrap().clone();
|
||||
let osu_client = Arc::new(OsuHttpClient::new(
|
||||
std::env::var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."),
|
||||
);
|
||||
));
|
||||
data.insert::<OsuClient>(osu_client.clone());
|
||||
data.insert::<oppai_cache::BeatmapCache>(oppai_cache::BeatmapCache::new(http_client));
|
||||
data.insert::<beatmap_cache::BeatmapMetaCache>(beatmap_cache::BeatmapMetaCache::new(
|
||||
|
@ -72,7 +70,7 @@ pub fn setup(
|
|||
));
|
||||
|
||||
// Announcer
|
||||
announcers.add(announcer::ANNOUNCER_KEY, announcer::updates);
|
||||
announcers.add(announcer::ANNOUNCER_KEY, announcer::Announcer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -88,8 +86,8 @@ struct Osu;
|
|||
#[description = "Receive information about an user in osu!std mode."]
|
||||
#[usage = "[username or user_id = your saved username]"]
|
||||
#[max_args(1)]
|
||||
pub fn std(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Std)
|
||||
pub async fn std(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Std).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
@ -97,8 +95,8 @@ pub fn std(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
|||
#[description = "Receive information about an user in osu!taiko mode."]
|
||||
#[usage = "[username or user_id = your saved username]"]
|
||||
#[max_args(1)]
|
||||
pub fn taiko(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Taiko)
|
||||
pub async fn taiko(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Taiko).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
@ -106,8 +104,8 @@ pub fn taiko(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
|||
#[description = "Receive information about an user in osu!catch mode."]
|
||||
#[usage = "[username or user_id = your saved username]"]
|
||||
#[max_args(1)]
|
||||
pub fn catch(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Catch)
|
||||
pub async fn catch(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Catch).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
@ -115,8 +113,8 @@ pub fn catch(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
|||
#[description = "Receive information about an user in osu!mania mode."]
|
||||
#[usage = "[username or user_id = your saved username]"]
|
||||
#[max_args(1)]
|
||||
pub fn mania(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Mania)
|
||||
pub async fn mania(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
get_user(ctx, msg, args, Mode::Mania).await
|
||||
}
|
||||
|
||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
||||
|
@ -137,17 +135,15 @@ impl AsRef<Beatmap> for BeatmapWithMode {
|
|||
#[description = "Save the given username as your username."]
|
||||
#[usage = "[username or user_id]"]
|
||||
#[num_args(1)]
|
||||
pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let osu = ctx.data.get_cloned::<OsuClient>();
|
||||
pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
|
||||
let user = args.single::<String>()?;
|
||||
let user: Option<User> = osu.user(UserID::Auto(user), |f| f)?;
|
||||
let user: Option<User> = osu.user(UserID::Auto(user), |f| f).await?;
|
||||
match user {
|
||||
Some(u) => {
|
||||
let db = OsuSavedUsers::open(&*ctx.data.read());
|
||||
let mut db = db.borrow_mut()?;
|
||||
|
||||
db.insert(
|
||||
OsuSavedUsers::open(&*data).borrow_mut()?.insert(
|
||||
msg.author.id,
|
||||
OsuUser {
|
||||
id: u.id,
|
||||
|
@ -161,10 +157,11 @@ pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
.push("user has been set to ")
|
||||
.push_mono_safe(u.username)
|
||||
.build(),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
None => {
|
||||
msg.reply(&ctx, "user not found...")?;
|
||||
msg.reply(&ctx, "user not found...").await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -187,7 +184,7 @@ impl FromStr for ModeArg {
|
|||
|
||||
fn to_user_id_query(
|
||||
s: Option<UsernameArg>,
|
||||
data: &ShareMap,
|
||||
data: &TypeMap,
|
||||
msg: &Message,
|
||||
) -> Result<UserID, Error> {
|
||||
let id = match s {
|
||||
|
@ -223,71 +220,69 @@ impl FromStr for Nth {
|
|||
}
|
||||
}
|
||||
|
||||
fn list_plays(plays: Vec<Score>, mode: Mode, ctx: Context, m: &Message) -> CommandResult {
|
||||
let watcher = ctx.data.get_cloned::<ReactionWatcher>();
|
||||
let osu = ctx.data.get_cloned::<BeatmapMetaCache>();
|
||||
let beatmap_cache = ctx.data.get_cloned::<BeatmapCache>();
|
||||
|
||||
async fn list_plays<'a>(
|
||||
plays: Vec<Score>,
|
||||
mode: Mode,
|
||||
ctx: &'a Context,
|
||||
m: &'a Message,
|
||||
) -> CommandResult {
|
||||
let plays = Arc::new(plays);
|
||||
if plays.is_empty() {
|
||||
m.reply(&ctx, "No plays found")?;
|
||||
m.reply(&ctx, "No plays found").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut beatmaps: Vec<Option<String>> = vec![None; plays.len()];
|
||||
|
||||
const ITEMS_PER_PAGE: usize = 5;
|
||||
let total_pages = (plays.len() + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
|
||||
watcher.paginate_fn(
|
||||
ctx,
|
||||
m.channel_id,
|
||||
move |page, e| {
|
||||
paginate(
|
||||
move |page, ctx, msg| {
|
||||
let plays = plays.clone();
|
||||
Box::pin(async move {
|
||||
let data = ctx.data.read().await;
|
||||
let osu = data.get::<BeatmapMetaCache>().unwrap();
|
||||
let beatmap_cache = data.get::<BeatmapCache>().unwrap();
|
||||
let page = page as usize;
|
||||
let start = page * ITEMS_PER_PAGE;
|
||||
let end = plays.len().min(start + ITEMS_PER_PAGE);
|
||||
if start >= end {
|
||||
return (e, Err(Error::from("No more pages")));
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let plays = &plays[start..end];
|
||||
let beatmaps = {
|
||||
let b = &mut beatmaps[start..end];
|
||||
b.par_iter_mut()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
v.get_or_insert_with(|| {
|
||||
if let Some(b) = osu.get_beatmap(plays[i].beatmap_id, mode).ok() {
|
||||
let stars = beatmap_cache
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.ok()
|
||||
.and_then(|b| {
|
||||
mode.to_oppai_mode().and_then(|mode| {
|
||||
b.get_info_with(Some(mode), plays[i].mods).ok()
|
||||
})
|
||||
})
|
||||
let beatmaps = plays
|
||||
.iter()
|
||||
.map(|play| async move {
|
||||
let beatmap = osu.get_beatmap(play.beatmap_id, mode).await?;
|
||||
let stars = {
|
||||
let b = beatmap_cache.get_beatmap(beatmap.beatmap_id).await?;
|
||||
mode.to_oppai_mode()
|
||||
.and_then(|mode| b.get_info_with(Some(mode), play.mods).ok())
|
||||
.map(|info| info.stars as f64)
|
||||
.unwrap_or(b.difficulty.stars);
|
||||
format!(
|
||||
.unwrap_or(beatmap.difficulty.stars)
|
||||
};
|
||||
let r: Result<_> = Ok(format!(
|
||||
"[{:.1}*] {} - {} [{}] ({})",
|
||||
stars,
|
||||
b.artist,
|
||||
b.title,
|
||||
b.difficulty_name,
|
||||
b.short_link(Some(mode), Some(plays[i].mods)),
|
||||
)
|
||||
} else {
|
||||
"FETCH_FAILED".to_owned()
|
||||
}
|
||||
beatmap.artist,
|
||||
beatmap.title,
|
||||
beatmap.difficulty_name,
|
||||
beatmap.short_link(Some(mode), Some(play.mods)),
|
||||
));
|
||||
r
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.map(|v| v.unwrap_or("FETCH_FAILED".to_owned()))
|
||||
.collect::<Vec<String>>();
|
||||
let pp = plays
|
||||
.iter()
|
||||
.map(|p| {
|
||||
p.pp.map(|pp| format!("{:.2}pp", pp))
|
||||
.or_else(|| {
|
||||
beatmap_cache.get_beatmap(p.beatmap_id).ok().and_then(|b| {
|
||||
mode.to_oppai_mode().and_then(|op| {
|
||||
.map(|p| async move {
|
||||
match p.pp.map(|pp| format!("{:.2}pp", pp)) {
|
||||
Some(v) => Ok(v),
|
||||
None => {
|
||||
let b = beatmap_cache.get_beatmap(p.beatmap_id).await?;
|
||||
let r: Result<_> = Ok(mode
|
||||
.to_oppai_mode()
|
||||
.and_then(|op| {
|
||||
b.get_pp_from(
|
||||
oppai_rs::Combo::NonFC {
|
||||
max_combo: p.max_combo as u32,
|
||||
|
@ -300,11 +295,15 @@ fn list_plays(plays: Vec<Score>, mode: Mode, ctx: Context, m: &Message) -> Comma
|
|||
.ok()
|
||||
.map(|pp| format!("{:.2}pp [?]", pp))
|
||||
})
|
||||
.unwrap_or("-".to_owned()));
|
||||
r
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or("-".to_owned())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.map(|v| v.unwrap_or("-".to_owned()))
|
||||
.collect::<Vec<String>>();
|
||||
let (beatmaps, pp) = future::join(beatmaps, pp).await;
|
||||
let pw = pp.iter().map(|v| v.len()).max().unwrap_or(2);
|
||||
/*mods width*/
|
||||
let mw = plays
|
||||
|
@ -364,10 +363,16 @@ fn list_plays(plays: Vec<Score>, mode: Mode, ctx: Context, m: &Message) -> Comma
|
|||
} else {
|
||||
m.push_line("[?] means pp was predicted by oppai-rs.");
|
||||
}
|
||||
(e.content(m.build()), Ok(()))
|
||||
msg.edit(ctx, |f| f.content(m.to_string())).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
ctx,
|
||||
m.channel_id,
|
||||
std::time::Duration::from_secs(60),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
@ -375,44 +380,49 @@ fn list_plays(plays: Vec<Score>, mode: Mode, ctx: Context, m: &Message) -> Comma
|
|||
#[usage = "#[the nth recent play = --all] / [mode (std, taiko, mania, catch) = std] / [username / user id = your saved id]"]
|
||||
#[example = "#1 / taiko / natsukagami"]
|
||||
#[max_args(3)]
|
||||
pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*data, msg)?;
|
||||
|
||||
let osu = ctx.data.get_cloned::<OsuClient>();
|
||||
let meta_cache = ctx.data.get_cloned::<BeatmapMetaCache>();
|
||||
let oppai = ctx.data.get_cloned::<BeatmapCache>();
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
let meta_cache = data.get::<BeatmapMetaCache>().unwrap();
|
||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||
let user = osu
|
||||
.user(user, |f| f.mode(mode))?
|
||||
.user(user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or(Error::from("User not found"))?;
|
||||
match nth {
|
||||
Nth::Nth(nth) => {
|
||||
let recent_play = osu
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))?
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(nth))
|
||||
.await?
|
||||
.into_iter()
|
||||
.last()
|
||||
.ok_or(Error::from("No such play"))?;
|
||||
let beatmap = meta_cache
|
||||
.get_beatmap(recent_play.beatmap_id, mode)
|
||||
.unwrap();
|
||||
let content = oppai.get_beatmap(beatmap.beatmap_id)?;
|
||||
let beatmap = meta_cache.get_beatmap(recent_play.beatmap_id, mode).await?;
|
||||
let content = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let beatmap_mode = BeatmapWithMode(beatmap, mode);
|
||||
|
||||
msg.channel_id.send_message(&ctx, |m| {
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |m| {
|
||||
m.content(format!(
|
||||
"{}: here is the play that you requested",
|
||||
msg.author
|
||||
))
|
||||
.embed(|m| score_embed(&recent_play, &beatmap_mode, &content, &user, None, m))
|
||||
})?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &beatmap_mode)?;
|
||||
cache::save_beatmap(&*data, msg.channel_id, &beatmap_mode)?;
|
||||
}
|
||||
Nth::All => {
|
||||
let plays = osu.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))?;
|
||||
list_plays(plays, mode, ctx.clone(), msg)?;
|
||||
let plays = osu
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
list_plays(plays, mode, ctx, msg).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -422,28 +432,33 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
#[description = "Show information from the last queried beatmap."]
|
||||
#[usage = "[mods = no mod]"]
|
||||
#[max_args(1)]
|
||||
pub fn last(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let b = cache::get_beatmap(&*ctx.data.read(), msg.channel_id)?;
|
||||
pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let b = cache::get_beatmap(&*data, msg.channel_id)?;
|
||||
|
||||
match b {
|
||||
Some(BeatmapWithMode(b, m)) => {
|
||||
let mods = args.find::<Mods>().unwrap_or(Mods::NOMOD);
|
||||
let info = ctx
|
||||
.data
|
||||
.get_cloned::<BeatmapCache>()
|
||||
.get_beatmap(b.beatmap_id)?
|
||||
let info = data
|
||||
.get::<BeatmapCache>()
|
||||
.unwrap()
|
||||
.get_beatmap(b.beatmap_id)
|
||||
.await?
|
||||
.get_info_with(m.to_oppai_mode(), mods)
|
||||
.ok();
|
||||
msg.channel_id.send_message(&ctx, |f| {
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |f| {
|
||||
f.content(format!(
|
||||
"{}: here is the beatmap you requested!",
|
||||
msg.author
|
||||
))
|
||||
.embed(|c| beatmap_embed(&b, m, mods, info, c))
|
||||
})?;
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
None => {
|
||||
msg.reply(&ctx, "No beatmap was queried on this channel.")?;
|
||||
msg.reply(&ctx, "No beatmap was queried on this channel.")
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,36 +469,43 @@ pub fn last(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
#[aliases("c", "chk")]
|
||||
#[description = "Check your own or someone else's best record on the last beatmap."]
|
||||
#[max_args(1)]
|
||||
pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let bm = cache::get_beatmap(&*ctx.data.read(), msg.channel_id)?;
|
||||
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let bm = cache::get_beatmap(&*data, msg.channel_id)?;
|
||||
|
||||
match bm {
|
||||
None => {
|
||||
msg.reply(&ctx, "No beatmap queried on this channel.")?;
|
||||
msg.reply(&ctx, "No beatmap queried on this channel.")
|
||||
.await?;
|
||||
}
|
||||
Some(bm) => {
|
||||
let b = &bm.0;
|
||||
let m = bm.1;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*data, msg)?;
|
||||
|
||||
let osu = ctx.data.get_cloned::<OsuClient>();
|
||||
let oppai = ctx.data.get_cloned::<BeatmapCache>();
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||
|
||||
let content = oppai.get_beatmap(b.beatmap_id)?;
|
||||
let content = oppai.get_beatmap(b.beatmap_id).await?;
|
||||
|
||||
let user = osu
|
||||
.user(user, |f| f)?
|
||||
.user(user, |f| f)
|
||||
.await?
|
||||
.ok_or(Error::from("User not found"))?;
|
||||
let scores = osu.scores(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))
|
||||
.await?;
|
||||
|
||||
if scores.is_empty() {
|
||||
msg.reply(&ctx, "No scores found")?;
|
||||
msg.reply(&ctx, "No scores found").await?;
|
||||
}
|
||||
|
||||
for score in scores.into_iter() {
|
||||
msg.channel_id.send_message(&ctx, |c| {
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |c| {
|
||||
c.embed(|m| score_embed(&score, &bm, &content, &user, None, m))
|
||||
})?;
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,27 +515,31 @@ pub fn check(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
|
||||
#[command]
|
||||
#[description = "Get the n-th top record of an user."]
|
||||
#[usage = "#[n-th = --all] / [mode (std, taiko, catch, mania) = std / [username or user_id = your saved user id]"]
|
||||
#[example = "#2 / taiko / natsukagami"]
|
||||
#[usage = "[mode (std, taiko, catch, mania)] = std / #[n-th = --all] / [username or user_id = your saved user id]"]
|
||||
#[example = "taiko / #2 / natsukagami"]
|
||||
#[max_args(3)]
|
||||
pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let mode = args
|
||||
.single::<ModeArg>()
|
||||
.map(|ModeArg(t)| t)
|
||||
.unwrap_or(Mode::Std);
|
||||
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*data, msg)?;
|
||||
|
||||
let osu = ctx.data.get_cloned::<OsuClient>();
|
||||
let oppai = ctx.data.get_cloned::<BeatmapCache>();
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||
let user = osu
|
||||
.user(user, |f| f.mode(mode))?
|
||||
.user(user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or(Error::from("User not found"))?;
|
||||
|
||||
match nth {
|
||||
Nth::Nth(nth) => {
|
||||
let top_play = osu.user_best(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))
|
||||
.await?;
|
||||
|
||||
let rank = top_play.len() as u8;
|
||||
|
||||
|
@ -524,66 +550,80 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
let beatmap = osu
|
||||
.beatmaps(BeatmapRequestKind::Beatmap(top_play.beatmap_id), |f| {
|
||||
f.mode(mode, true)
|
||||
})?
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
let content = oppai.get_beatmap(beatmap.beatmap_id)?;
|
||||
let content = oppai.get_beatmap(beatmap.beatmap_id).await?;
|
||||
let beatmap = BeatmapWithMode(beatmap, mode);
|
||||
|
||||
msg.channel_id.send_message(&ctx, |m| {
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |m| {
|
||||
m.content(format!(
|
||||
"{}: here is the play that you requested",
|
||||
msg.author
|
||||
))
|
||||
.embed(|m| score_embed(&top_play, &beatmap, &content, &user, Some(rank), m))
|
||||
})?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Save the beatmap...
|
||||
cache::save_beatmap(&*ctx.data.read(), msg.channel_id, &beatmap)?;
|
||||
cache::save_beatmap(&*data, msg.channel_id, &beatmap)?;
|
||||
}
|
||||
Nth::All => {
|
||||
let plays = osu.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))?;
|
||||
list_plays(plays, mode, ctx.clone(), msg)?;
|
||||
let plays = osu
|
||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
.await?;
|
||||
list_plays(plays, mode, ctx, msg).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_user(ctx: &mut Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult {
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*ctx.data.read(), msg)?;
|
||||
let osu = ctx.data.get_cloned::<OsuClient>();
|
||||
let cache = ctx.data.get_cloned::<BeatmapMetaCache>();
|
||||
let user = osu.user(user, |f| f.mode(mode))?;
|
||||
let oppai = ctx.data.get_cloned::<BeatmapCache>();
|
||||
async fn get_user(ctx: &Context, msg: &Message, mut args: Args, mode: Mode) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), &*data, msg)?;
|
||||
let osu = data.get::<OsuClient>().unwrap();
|
||||
let cache = data.get::<BeatmapMetaCache>().unwrap();
|
||||
let user = osu.user(user, |f| f.mode(mode)).await?;
|
||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||
match user {
|
||||
Some(u) => {
|
||||
let best = osu
|
||||
.user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))?
|
||||
let best = match osu
|
||||
.user_best(UserID::ID(u.id), |f| f.limit(1).mode(mode))
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|m| -> Result<_, Error> {
|
||||
let beatmap = cache.get_beatmap(m.beatmap_id, mode)?;
|
||||
let info = mode
|
||||
.to_oppai_mode()
|
||||
.map(|mode| -> Result<_, Error> {
|
||||
Ok(oppai
|
||||
.get_beatmap(m.beatmap_id)?
|
||||
.get_info_with(Some(mode), m.mods)?)
|
||||
})
|
||||
.transpose()?;
|
||||
Ok((m, BeatmapWithMode(beatmap, mode), info))
|
||||
})
|
||||
.transpose()?;
|
||||
msg.channel_id.send_message(&ctx, |m| {
|
||||
{
|
||||
Some(m) => {
|
||||
let beatmap = cache.get_beatmap(m.beatmap_id, mode).await?;
|
||||
let info = match mode.to_oppai_mode() {
|
||||
Some(mode) => Some(
|
||||
oppai
|
||||
.get_beatmap(m.beatmap_id)
|
||||
.await?
|
||||
.get_info_with(Some(mode), m.mods)?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
Some((m, BeatmapWithMode(beatmap, mode), info))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
msg.channel_id
|
||||
.send_message(&ctx, |m| {
|
||||
m.content(format!(
|
||||
"{}: here is the user that you requested",
|
||||
msg.author
|
||||
))
|
||||
.embed(|m| user_embed(u, best, m))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
None => msg.reply(&ctx, "🔍 user not found!"),
|
||||
}?;
|
||||
None => {
|
||||
msg.reply(&ctx, "🔍 user not found!").await?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use super::{db::OsuSavedUsers, ModeArg};
|
||||
use crate::models::Mode;
|
||||
use serenity::{
|
||||
builder::EditMessage,
|
||||
framework::standard::{macros::command, Args, CommandError as Error, CommandResult},
|
||||
framework::standard::{macros::command, Args, CommandResult},
|
||||
model::channel::Message,
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
|
@ -15,15 +14,15 @@ const ITEMS_PER_PAGE: usize = 10;
|
|||
#[usage = "[mode (Std, Taiko, Catch, Mania) = Std]"]
|
||||
#[max_args(1)]
|
||||
#[only_in(guilds)]
|
||||
pub fn server_rank(ctx: &mut Context, m: &Message, mut args: Args) -> CommandResult {
|
||||
pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let mode = args.single::<ModeArg>().map(|v| v.0).unwrap_or(Mode::Std);
|
||||
let guild = m.guild_id.expect("Guild-only command");
|
||||
let users = OsuSavedUsers::open(&*ctx.data.read())
|
||||
.borrow()
|
||||
.expect("DB initialized")
|
||||
.iter()
|
||||
.filter_map(|(user_id, osu_user)| {
|
||||
guild.member(&ctx, user_id).ok().and_then(|member| {
|
||||
let users = OsuSavedUsers::open(&*data).borrow()?.clone();
|
||||
let users = users
|
||||
.into_iter()
|
||||
.map(|(user_id, osu_user)| async move {
|
||||
guild.member(&ctx, user_id).await.ok().and_then(|member| {
|
||||
osu_user
|
||||
.pp
|
||||
.get(mode as usize)
|
||||
|
@ -32,7 +31,10 @@ pub fn server_rank(ctx: &mut Context, m: &Message, mut args: Args) -> CommandRes
|
|||
.map(|pp| (pp, member.distinct(), osu_user.last_update.clone()))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<stream::FuturesUnordered<_>>()
|
||||
.filter_map(|v| future::ready(v))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let last_update = users.iter().map(|(_, _, a)| a).min().cloned();
|
||||
let mut users = users
|
||||
.into_iter()
|
||||
|
@ -41,18 +43,21 @@ pub fn server_rank(ctx: &mut Context, m: &Message, mut args: Args) -> CommandRes
|
|||
users.sort_by(|(a, _), (b, _)| (*b).partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
if users.is_empty() {
|
||||
m.reply(&ctx, "No saved users in the current server...")?;
|
||||
m.reply(&ctx, "No saved users in the current server...")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let users = std::sync::Arc::new(users);
|
||||
let last_update = last_update.unwrap();
|
||||
ctx.data.get_cloned::<ReactionWatcher>().paginate_fn(
|
||||
ctx.clone(),
|
||||
m.channel_id,
|
||||
move |page: u8, e: &mut EditMessage| {
|
||||
paginate(
|
||||
move |page: u8, ctx: &Context, m: &mut Message| {
|
||||
let users = users.clone();
|
||||
Box::pin(async move {
|
||||
let start = (page as usize) * ITEMS_PER_PAGE;
|
||||
let end = (start + ITEMS_PER_PAGE).min(users.len());
|
||||
if start >= end {
|
||||
return (e, Err(Error("No more items".to_owned())));
|
||||
return Ok(false);
|
||||
}
|
||||
let total_len = users.len();
|
||||
let users = &users[start..end];
|
||||
|
@ -77,10 +82,15 @@ pub fn server_rank(ctx: &mut Context, m: &Message, mut args: Args) -> CommandRes
|
|||
(total_len + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE,
|
||||
last_update.to_rfc2822()
|
||||
));
|
||||
(e.content(content.build()), Ok(()))
|
||||
m.edit(ctx, |f| f.content(content.to_string())).await?;
|
||||
Ok(true)
|
||||
})
|
||||
},
|
||||
ctx,
|
||||
m.channel_id,
|
||||
std::time::Duration::from_secs(60),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -9,14 +9,30 @@ use models::*;
|
|||
use request::builders::*;
|
||||
use request::*;
|
||||
use reqwest::{Client as HTTPClient, RequestBuilder, Response};
|
||||
use std::convert::TryInto;
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
use tower;
|
||||
use youmubot_prelude::*;
|
||||
|
||||
/// The number of requests per minute to the osu! server.
|
||||
const REQUESTS_PER_MINUTE: u64 = 50;
|
||||
|
||||
type BoxedResp =
|
||||
std::pin::Pin<Box<dyn future::Future<Output = Result<Response, reqwest::Error>> + Send>>;
|
||||
/// Client is the client that will perform calls to the osu! api server.
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
http_client: RwLock<
|
||||
Box<
|
||||
dyn tower::Service<
|
||||
reqwest::Request,
|
||||
Response = Response,
|
||||
Error = reqwest::Error,
|
||||
Future = BoxedResp,
|
||||
> + Send
|
||||
+ Sync,
|
||||
>,
|
||||
>,
|
||||
client: Arc<HTTPClient>,
|
||||
key: String,
|
||||
client: HTTPClient,
|
||||
}
|
||||
|
||||
fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
|
||||
|
@ -31,17 +47,24 @@ 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(http_client: HTTPClient, key: String) -> Client {
|
||||
pub fn new(key: String) -> Client {
|
||||
let http_client = Arc::new(HTTPClient::new());
|
||||
let _http = http_client.clone();
|
||||
let srv = tower::ServiceBuilder::new()
|
||||
.rate_limit(REQUESTS_PER_MINUTE, std::time::Duration::from_secs(60))
|
||||
.service(tower::service_fn(move |req| -> BoxedResp {
|
||||
Box::pin(_http.execute(req))
|
||||
}));
|
||||
Client {
|
||||
key,
|
||||
http_client: RwLock::new(Box::new(srv)),
|
||||
client: http_client,
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_request(&self, r: RequestBuilder) -> Result<Response> {
|
||||
let v = r.query(&[("k", &*self.key)]).build()?;
|
||||
// dbg!(v.url());
|
||||
Ok(self.client.execute(v).await?)
|
||||
Ok(self.http_client.write().await.call(v).await?)
|
||||
}
|
||||
|
||||
pub async fn beatmaps(
|
||||
|
|
Loading…
Add table
Reference in a new issue