mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Implement recent
This commit is contained in:
parent
4020405801
commit
3af0b56755
4 changed files with 235 additions and 8 deletions
|
@ -17,9 +17,13 @@ impl<'de> Deserialize<'de> for Score {
|
||||||
user_id: parse_from_str(&raw.user_id)?,
|
user_id: parse_from_str(&raw.user_id)?,
|
||||||
date: parse_date(&raw.date)?,
|
date: parse_date(&raw.date)?,
|
||||||
beatmap_id: raw.beatmap_id.map(parse_from_str).transpose()?.unwrap_or(0),
|
beatmap_id: raw.beatmap_id.map(parse_from_str).transpose()?.unwrap_or(0),
|
||||||
replay_available: parse_bool(&raw.replay_available)?,
|
replay_available: raw
|
||||||
|
.replay_available
|
||||||
|
.map(parse_bool)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(false),
|
||||||
score: parse_from_str(&raw.score)?,
|
score: parse_from_str(&raw.score)?,
|
||||||
pp: parse_from_str(&raw.pp)?,
|
pp: raw.pp.map(parse_from_str).transpose()?,
|
||||||
rank: parse_from_str(&raw.rank)?,
|
rank: parse_from_str(&raw.rank)?,
|
||||||
mods: {
|
mods: {
|
||||||
let v: u64 = parse_from_str(&raw.enabled_mods)?;
|
let v: u64 = parse_from_str(&raw.enabled_mods)?;
|
||||||
|
|
|
@ -159,6 +159,14 @@ impl Beatmap {
|
||||||
self.beatmapset_id, NEW_MODE_NAMES[self.mode as usize], self.beatmap_id
|
self.beatmapset_id, NEW_MODE_NAMES[self.mode as usize], self.beatmap_id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Link to the cover image of the beatmap.
|
||||||
|
pub fn cover_url(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg",
|
||||||
|
self.beatmapset_id
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -198,6 +206,16 @@ pub struct User {
|
||||||
pub accuracy: f64,
|
pub accuracy: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn link(&self) -> String {
|
||||||
|
format!("https://osu.ppy.sh/users/{}", self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avatar_url(&self) -> String {
|
||||||
|
format!("https://a.ppy.sh/{}", self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Rank {
|
pub enum Rank {
|
||||||
SS,
|
SS,
|
||||||
|
@ -244,7 +262,7 @@ pub struct Score {
|
||||||
pub beatmap_id: u64,
|
pub beatmap_id: u64,
|
||||||
|
|
||||||
pub score: u64,
|
pub score: u64,
|
||||||
pub pp: f64,
|
pub pp: Option<f64>,
|
||||||
pub rank: Rank,
|
pub rank: Rank,
|
||||||
pub mods: Mods, // Later
|
pub mods: Mods, // Later
|
||||||
|
|
||||||
|
@ -257,3 +275,41 @@ pub struct Score {
|
||||||
pub max_combo: u64,
|
pub max_combo: u64,
|
||||||
pub perfect: bool,
|
pub perfect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Score {
|
||||||
|
/// Given the play's mode, calculate the score's accuracy.
|
||||||
|
pub fn accuracy(&self, mode: Mode) -> f64 {
|
||||||
|
100.0
|
||||||
|
* match mode {
|
||||||
|
Mode::Std => {
|
||||||
|
(6 * self.count_300 + 2 * self.count_100 + self.count_50) as f64
|
||||||
|
/ (6.0
|
||||||
|
* (self.count_300 + self.count_100 + self.count_50 + self.count_miss)
|
||||||
|
as f64)
|
||||||
|
}
|
||||||
|
Mode::Taiko => {
|
||||||
|
(2 * self.count_300 + self.count_100) as f64
|
||||||
|
/ 2.0
|
||||||
|
/ (self.count_300 + self.count_100 + self.count_miss) as f64
|
||||||
|
}
|
||||||
|
Mode::Catch => {
|
||||||
|
(self.count_300 + self.count_100) as f64
|
||||||
|
/ (self.count_300 + self.count_100 + self.count_miss + self.count_katu/* # of droplet misses */)
|
||||||
|
as f64
|
||||||
|
}
|
||||||
|
Mode::Mania => {
|
||||||
|
((self.count_geki /* MAX */ + self.count_300) * 6
|
||||||
|
+ self.count_katu /* 200 */ * 4
|
||||||
|
+ self.count_100 * 2
|
||||||
|
+ self.count_50) as f64
|
||||||
|
/ 6.0
|
||||||
|
/ (self.count_geki
|
||||||
|
+ self.count_300
|
||||||
|
+ self.count_katu
|
||||||
|
+ self.count_100
|
||||||
|
+ self.count_50
|
||||||
|
+ self.count_miss) as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -93,6 +93,6 @@ pub(crate) struct Score {
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub rank: String,
|
pub rank: String,
|
||||||
pub pp: String,
|
pub pp: Option<String>,
|
||||||
pub replay_available: String,
|
pub replay_available: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,15 @@ use serenity::{
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
use youmubot_osu::{
|
use youmubot_osu::{
|
||||||
models::{Beatmap, Mode, Score, User},
|
models::{Beatmap, Mode, Rank, Score, User},
|
||||||
request::{BeatmapRequestKind, UserID},
|
request::{BeatmapRequestKind, UserID},
|
||||||
|
Client as OsuClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod hook;
|
mod hook;
|
||||||
|
|
||||||
pub use hook::hook;
|
pub use hook::hook;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
group!({
|
group!({
|
||||||
name: "osu",
|
name: "osu",
|
||||||
|
@ -27,7 +29,7 @@ group!({
|
||||||
prefix: "osu",
|
prefix: "osu",
|
||||||
description: "osu! related commands.",
|
description: "osu! related commands.",
|
||||||
},
|
},
|
||||||
commands: [std, taiko, catch, mania, save],
|
commands: [std, taiko, catch, mania, save, recent],
|
||||||
});
|
});
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
@ -101,6 +103,96 @@ pub fn save(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ModeArg(Mode);
|
||||||
|
|
||||||
|
impl FromStr for ModeArg {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(ModeArg(match s {
|
||||||
|
"std" => Mode::Std,
|
||||||
|
"taiko" => Mode::Taiko,
|
||||||
|
"catch" => Mode::Catch,
|
||||||
|
"mania" => Mode::Mania,
|
||||||
|
_ => return Err(Error::from(format!("Unknown mode {}", s))),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description = "Gets an user's recent play"]
|
||||||
|
#[usage = "#[the nth recent play = 1] / [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 {
|
||||||
|
struct Nth(u8);
|
||||||
|
|
||||||
|
impl FromStr for Nth {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if !s.starts_with("#") {
|
||||||
|
Err(Error::from("Not an order"))
|
||||||
|
} else {
|
||||||
|
let v = s.split_at("#".len()).1.parse()?;
|
||||||
|
Ok(Nth(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = match args.single::<String>() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
let db: DBWriteGuard<_> = data
|
||||||
|
.get_mut::<OsuSavedUsers>()
|
||||||
|
.ok_or(Error::from("DB uninitialized"))?
|
||||||
|
.into();
|
||||||
|
let db = db.borrow()?;
|
||||||
|
match db.get(&msg.author.id) {
|
||||||
|
Some(ref v) => v.to_string(),
|
||||||
|
None => {
|
||||||
|
msg.reply(&ctx, "You have not saved any account.")?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg!((nth, &mode, &user, &args));
|
||||||
|
|
||||||
|
let reqwest = data.get::<http::HTTP>().unwrap();
|
||||||
|
let osu: &OsuClient = data.get::<http::Osu>().unwrap();
|
||||||
|
let user = osu
|
||||||
|
.user(reqwest, UserID::Auto(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))?
|
||||||
|
.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),
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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, &user, &mode, None, m))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> CommandResult {
|
fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> CommandResult {
|
||||||
let mut data = ctx.data.write();
|
let mut data = ctx.data.write();
|
||||||
let username = match args.remains() {
|
let username = match args.remains() {
|
||||||
|
@ -147,6 +239,78 @@ fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> Command
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn score_embed<'a>(
|
||||||
|
s: &Score,
|
||||||
|
b: &Beatmap,
|
||||||
|
u: &User,
|
||||||
|
mode: &Mode,
|
||||||
|
top_record: Option<u8>,
|
||||||
|
m: &'a mut CreateEmbed,
|
||||||
|
) -> &'a mut CreateEmbed {
|
||||||
|
let accuracy = s.accuracy(*mode);
|
||||||
|
let score_line = match &s.rank {
|
||||||
|
Rank::SS | Rank::SSH => format!("SS"),
|
||||||
|
_ if s.perfect => format!("{:2}% FC", accuracy),
|
||||||
|
Rank::F => format!("{:.2}% {} combo [FAILED]", accuracy, s.max_combo),
|
||||||
|
v => format!("{:.2}% {} combo {} rank", accuracy, s.max_combo, v),
|
||||||
|
};
|
||||||
|
let score_line =
|
||||||
|
s.pp.map(|pp| format!("{} | {:2}pp", &score_line, pp))
|
||||||
|
.unwrap_or(score_line);
|
||||||
|
let top_record = top_record
|
||||||
|
.map(|v| format!("| #{} top record!", v))
|
||||||
|
.unwrap_or("".to_owned());
|
||||||
|
m.author(|f| f.name(&u.username).url(u.link()).icon_url(u.avatar_url()))
|
||||||
|
.color(0xffb6c1)
|
||||||
|
.title(format!(
|
||||||
|
"{} | {} - {} [{}] {} ({:.2}\\*) by {} | {} {}",
|
||||||
|
u.username,
|
||||||
|
b.artist,
|
||||||
|
b.title,
|
||||||
|
b.difficulty_name,
|
||||||
|
s.mods,
|
||||||
|
b.difficulty.stars,
|
||||||
|
b.creator,
|
||||||
|
score_line,
|
||||||
|
top_record
|
||||||
|
))
|
||||||
|
.description(format!("[[Beatmap]]({})", b.link()))
|
||||||
|
.image(b.cover_url())
|
||||||
|
.field(
|
||||||
|
"Beatmap",
|
||||||
|
format!("{} - {} [{}]", b.artist, b.title, b.difficulty_name),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.field("Rank", &score_line, false)
|
||||||
|
.fields(s.pp.map(|pp| ("pp gained", format!("{:2}pp", pp), true)))
|
||||||
|
.field("Creator", &b.creator, true)
|
||||||
|
.field("Mode", mode.to_string(), true)
|
||||||
|
.field(
|
||||||
|
"Map stats",
|
||||||
|
MessageBuilder::new()
|
||||||
|
.push(format!("[[Link]]({})", b.link()))
|
||||||
|
.push(", ")
|
||||||
|
.push_bold(format!("{:.2}⭐", b.difficulty.stars))
|
||||||
|
.push(", ")
|
||||||
|
.push_bold_line(
|
||||||
|
b.mode.to_string() + if b.mode == *mode { "" } else { " (Converted)" },
|
||||||
|
)
|
||||||
|
.push("CS")
|
||||||
|
.push_bold(format!("{:.1}", b.difficulty.cs))
|
||||||
|
.push(", AR")
|
||||||
|
.push_bold(format!("{:.1}", b.difficulty.ar))
|
||||||
|
.push(", OD")
|
||||||
|
.push_bold(format!("{:.1}", b.difficulty.od))
|
||||||
|
.push(", HP")
|
||||||
|
.push_bold(format!("{:.1}", b.difficulty.hp))
|
||||||
|
.push(", ⌛ ")
|
||||||
|
.push_bold(format!("{}", Duration(b.drain_length)))
|
||||||
|
.build(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.field("Played on", s.date.format("%F %T"), false)
|
||||||
|
}
|
||||||
|
|
||||||
fn user_embed<'a>(
|
fn user_embed<'a>(
|
||||||
u: User,
|
u: User,
|
||||||
best: Option<(Score, Beatmap)>,
|
best: Option<(Score, Beatmap)>,
|
||||||
|
@ -195,7 +359,10 @@ fn user_embed<'a>(
|
||||||
(
|
(
|
||||||
"Best Record",
|
"Best Record",
|
||||||
MessageBuilder::new()
|
MessageBuilder::new()
|
||||||
.push_bold(format!("{:.2}pp", v.pp))
|
.push_bold(format!(
|
||||||
|
"{:.2}pp",
|
||||||
|
v.pp.unwrap() /*Top record should have pp*/
|
||||||
|
))
|
||||||
.push(" - ")
|
.push(" - ")
|
||||||
.push_line(format!("{:.1} ago", Duration(Utc::now() - v.date)))
|
.push_line(format!("{:.1} ago", Duration(Utc::now() - v.date)))
|
||||||
.push("on ")
|
.push("on ")
|
||||||
|
|
Loading…
Add table
Reference in a new issue