mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 08:48:54 +00:00
Simplify lb
This commit is contained in:
parent
48bd49b3bd
commit
c2562fbbca
3 changed files with 63 additions and 184 deletions
|
@ -111,19 +111,6 @@ impl OsuUserBests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuUserBests {
|
impl OsuUserBests {
|
||||||
pub async fn by_beatmap(&self, beatmap_id: u64, mode: Mode) -> Result<Vec<(UserId, Score)>> {
|
|
||||||
let scores = models::UserBestScore::by_map(beatmap_id as i64, mode as u8, &self.0).await?;
|
|
||||||
Ok(scores
|
|
||||||
.into_iter()
|
|
||||||
.map(|us| {
|
|
||||||
(
|
|
||||||
UserId(us.user_id as u64),
|
|
||||||
bincode::deserialize(&us.score[..]).unwrap(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn save(
|
pub async fn save(
|
||||||
&self,
|
&self,
|
||||||
user: impl Into<UserId>,
|
user: impl Into<UserId>,
|
||||||
|
|
|
@ -33,7 +33,7 @@ use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests};
|
||||||
use embeds::{beatmap_embed, score_embed, user_embed};
|
use embeds::{beatmap_embed, score_embed, user_embed};
|
||||||
use hook::SHORT_LINK_REGEX;
|
use hook::SHORT_LINK_REGEX;
|
||||||
pub use hook::{dot_osu_hook, hook};
|
pub use hook::{dot_osu_hook, hook};
|
||||||
use server_rank::{SERVER_RANK_COMMAND, UPDATE_LEADERBOARD_COMMAND};
|
use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
|
||||||
|
|
||||||
/// The osu! client.
|
/// The osu! client.
|
||||||
pub(crate) struct OsuClient;
|
pub(crate) struct OsuClient;
|
||||||
|
@ -64,11 +64,6 @@ pub async fn setup(
|
||||||
data.insert::<OsuLastBeatmap>(OsuLastBeatmap::new(sql_client.clone()));
|
data.insert::<OsuLastBeatmap>(OsuLastBeatmap::new(sql_client.clone()));
|
||||||
data.insert::<OsuUserBests>(OsuUserBests::new(sql_client.clone()));
|
data.insert::<OsuUserBests>(OsuUserBests::new(sql_client.clone()));
|
||||||
|
|
||||||
// Locks
|
|
||||||
data.insert::<server_rank::update_lock::UpdateLock>(
|
|
||||||
server_rank::update_lock::UpdateLock::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// API client
|
// API client
|
||||||
let http_client = data.get::<HTTPClient>().unwrap().clone();
|
let http_client = data.get::<HTTPClient>().unwrap().clone();
|
||||||
let mk_osu_client = || async {
|
let mk_osu_client = || async {
|
||||||
|
@ -120,7 +115,7 @@ pub async fn setup(
|
||||||
check,
|
check,
|
||||||
top,
|
top,
|
||||||
server_rank,
|
server_rank,
|
||||||
update_leaderboard,
|
show_leaderboard,
|
||||||
clean_cache
|
clean_cache
|
||||||
)]
|
)]
|
||||||
#[default_command(std)]
|
#[default_command(std)]
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use super::{
|
use super::{db::OsuSavedUsers, ModeArg, OsuClient};
|
||||||
db::{OsuSavedUsers, OsuUserBests},
|
|
||||||
ModeArg, OsuClient,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
discord::{
|
discord::{
|
||||||
display::ScoreListStyle,
|
display::ScoreListStyle,
|
||||||
oppai_cache::{Accuracy, BeatmapCache},
|
oppai_cache::{Accuracy, BeatmapCache},
|
||||||
BeatmapWithMode,
|
|
||||||
},
|
},
|
||||||
models::{Mode, Mods, Score},
|
models::{Mode, Mods},
|
||||||
request::UserID,
|
request::UserID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
framework::standard::{macros::command, Args, CommandResult},
|
framework::standard::{macros::command, Args, CommandResult},
|
||||||
model::{channel::Message, id::UserId},
|
model::channel::Message,
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
@ -166,39 +162,6 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod update_lock {
|
|
||||||
use serenity::{model::id::GuildId, prelude::TypeMapKey};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct UpdateLock(Mutex<HashSet<GuildId>>);
|
|
||||||
|
|
||||||
pub struct UpdateLockGuard<'a>(&'a UpdateLock, GuildId);
|
|
||||||
|
|
||||||
impl TypeMapKey for UpdateLock {
|
|
||||||
type Value = UpdateLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UpdateLock {
|
|
||||||
pub fn get(&self, guild: GuildId) -> Option<UpdateLockGuard> {
|
|
||||||
let mut set = self.0.lock().unwrap();
|
|
||||||
if set.contains(&guild) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
set.insert(guild);
|
|
||||||
Some(UpdateLockGuard(self, guild))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for UpdateLockGuard<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let mut set = self.0 .0.lock().unwrap();
|
|
||||||
set.remove(&self.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum OrderBy {
|
enum OrderBy {
|
||||||
PP,
|
PP,
|
||||||
|
@ -229,21 +192,13 @@ impl std::str::FromStr for OrderBy {
|
||||||
#[description = "See the server's ranks on the last seen beatmap"]
|
#[description = "See the server's ranks on the last seen beatmap"]
|
||||||
#[max_args(2)]
|
#[max_args(2)]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
let sort_order = args.single::<OrderBy>().unwrap_or_default();
|
let order = args.single::<OrderBy>().unwrap_or_default();
|
||||||
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
let style = args.single::<ScoreListStyle>().unwrap_or_default();
|
||||||
|
|
||||||
let guild = m.guild_id.unwrap();
|
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let update_lock = data.get::<update_lock::UpdateLock>().unwrap();
|
|
||||||
let update_lock = match update_lock.get(guild) {
|
let (bm, _) = match super::load_beatmap(ctx, m).await {
|
||||||
None => {
|
|
||||||
m.reply(&ctx, "Another update is running.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Some(v) => v,
|
|
||||||
};
|
|
||||||
let (bm, mods) = match super::load_beatmap(ctx, m).await {
|
|
||||||
Some((bm, mods_def)) => {
|
Some((bm, mods_def)) => {
|
||||||
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
|
||||||
(bm, mods)
|
(bm, mods)
|
||||||
|
@ -253,135 +208,77 @@ pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> C
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mode = bm.1;
|
|
||||||
let member_cache = data.get::<MemberCache>().unwrap();
|
|
||||||
// Signal that we are running.
|
|
||||||
let running_reaction = m.react(&ctx, '⌛').await?;
|
|
||||||
|
|
||||||
// Run a check on everyone in the server basically.
|
let osu = data.get::<OsuClient>().unwrap().clone();
|
||||||
let all_server_users: Vec<(UserId, Vec<Score>)> = {
|
|
||||||
let osu = data.get::<OsuClient>().unwrap();
|
// Get oppai map.
|
||||||
|
let mode = bm.1;
|
||||||
|
let oppai = data.get::<BeatmapCache>().unwrap();
|
||||||
|
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
|
||||||
|
|
||||||
|
let guild = m.guild_id.expect("Guild-only command");
|
||||||
|
let scores = {
|
||||||
|
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
|
||||||
|
// Signal that we are running.
|
||||||
|
let running_reaction = m.react(&ctx, '⌛').await?;
|
||||||
|
|
||||||
let osu_users = data
|
let osu_users = data
|
||||||
.get::<OsuSavedUsers>()
|
.get::<OsuSavedUsers>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.all()
|
.all()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|osu_user| (osu_user.user_id, osu_user.id));
|
.map(|v| (v.user_id, v))
|
||||||
let beatmap_id = bm.0.beatmap_id;
|
.collect::<HashMap<_, _>>();
|
||||||
osu_users
|
let mut scores = guild
|
||||||
.map(|(user_id, osu_id)| {
|
.members_iter(&ctx)
|
||||||
member_cache
|
.filter_map(|mem| {
|
||||||
.query(&ctx, user_id, guild)
|
future::ready(
|
||||||
.map(move |t| t.map(|_| (user_id, osu_id)))
|
mem.ok()
|
||||||
|
.and_then(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id))),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<stream::FuturesUnordered<_>>()
|
.filter_map(|(mem, osu_id)| {
|
||||||
.filter_map(future::ready)
|
osu.scores(bm.0.beatmap_id, move |f| {
|
||||||
.filter_map(|(member, osu_id)| async move {
|
f.user(UserID::ID(osu_id)).mode(bm.1)
|
||||||
let scores = osu
|
})
|
||||||
.scores(beatmap_id, |f| f.user(UserID::ID(osu_id)).mode(mode))
|
.map(|r| Some((mem, r.ok()?)))
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
scores
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.map(|scores| (member, scores))
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await
|
.await
|
||||||
};
|
|
||||||
let updated_users = all_server_users.len();
|
|
||||||
// Update everything.
|
|
||||||
{
|
|
||||||
let db = data.get::<OsuUserBests>().unwrap();
|
|
||||||
all_server_users
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(u, scores)| db.save(u, mode, scores))
|
.flat_map(|(mem, scores)| {
|
||||||
.collect::<stream::FuturesUnordered<_>>()
|
let mem = Arc::new(mem);
|
||||||
.try_collect::<()>()
|
scores
|
||||||
.await?;
|
.into_iter()
|
||||||
}
|
.filter_map(|score| {
|
||||||
// Signal update complete.
|
let pp = score.pp.or_else(|| {
|
||||||
running_reaction.delete(&ctx).await.ok();
|
oppai_map
|
||||||
m.reply(
|
.get_pp_from(
|
||||||
&ctx,
|
mode,
|
||||||
format!(
|
Some(score.max_combo as usize),
|
||||||
"update for beatmap (`{}`) complete, {} users updated.",
|
Accuracy::ByCount(
|
||||||
bm.0.short_link(if bm.mode() != bm.1 { Some(bm.1) } else { None }, None),
|
score.count_300,
|
||||||
updated_users
|
score.count_100,
|
||||||
),
|
score.count_50,
|
||||||
)
|
score.count_miss,
|
||||||
.await
|
),
|
||||||
.ok();
|
score.mods,
|
||||||
drop(update_lock);
|
)
|
||||||
show_leaderboard(ctx, m, bm, mods, sort_order, style).await
|
.ok()
|
||||||
}
|
})?;
|
||||||
|
Some((pp, mem.clone(), score))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
async fn show_leaderboard(
|
running_reaction.delete(&ctx).await?;
|
||||||
ctx: &Context,
|
|
||||||
m: &Message,
|
|
||||||
bm: BeatmapWithMode,
|
|
||||||
mods: Mods,
|
|
||||||
order: OrderBy,
|
|
||||||
style: ScoreListStyle,
|
|
||||||
) -> CommandResult {
|
|
||||||
let data = ctx.data.read().await;
|
|
||||||
|
|
||||||
// Get oppai map.
|
|
||||||
let mode = bm.1;
|
|
||||||
let oppai = data.get::<BeatmapCache>().unwrap();
|
|
||||||
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
|
|
||||||
let get_oppai_pp = move |combo: u64, acc: Accuracy, mods: Mods| {
|
|
||||||
oppai_map
|
|
||||||
.get_pp_from(mode, Some(combo as usize), acc, mods)
|
|
||||||
.ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
let guild = m.guild_id.expect("Guild-only command");
|
|
||||||
let member_cache = data.get::<MemberCache>().unwrap();
|
|
||||||
let scores = {
|
|
||||||
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
|
|
||||||
|
|
||||||
let scores = data
|
|
||||||
.get::<OsuUserBests>()
|
|
||||||
.unwrap()
|
|
||||||
.by_beatmap(bm.0.beatmap_id, bm.1)
|
|
||||||
.await?;
|
|
||||||
if scores.is_empty() {
|
if scores.is_empty() {
|
||||||
m.reply(&ctx, NO_SCORES).await?;
|
m.reply(&ctx, NO_SCORES).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut scores: Vec<(f64, String, Score)> = scores
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, score)| score.mods.contains(mods))
|
|
||||||
.map(|(user_id, score)| {
|
|
||||||
member_cache
|
|
||||||
.query(&ctx, user_id, guild)
|
|
||||||
.map(|m| m.map(move |m| (m.distinct(), score)))
|
|
||||||
})
|
|
||||||
.collect::<stream::FuturesUnordered<_>>()
|
|
||||||
.filter_map(future::ready)
|
|
||||||
.filter_map(|(user, score)| {
|
|
||||||
future::ready(
|
|
||||||
score
|
|
||||||
.pp
|
|
||||||
.or_else(|| {
|
|
||||||
get_oppai_pp(
|
|
||||||
score.max_combo,
|
|
||||||
Accuracy::ByCount(
|
|
||||||
score.count_300,
|
|
||||||
score.count_100,
|
|
||||||
score.count_50,
|
|
||||||
score.count_miss,
|
|
||||||
),
|
|
||||||
score.mods,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|v| (v, user, score)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await;
|
|
||||||
match order {
|
match order {
|
||||||
OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| {
|
OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| {
|
||||||
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
|
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
|
Loading…
Add table
Reference in a new issue