mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 00:08:54 +00:00
Add beatmap command
This commit is contained in:
parent
711da6fa34
commit
6105ee610f
11 changed files with 252 additions and 60 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
@ -104,7 +104,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -461,7 +461,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -472,7 +472,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -780,7 +780,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1464,7 +1464,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1575,7 +1575,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1643,7 +1643,7 @@ dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1660,9 +1660,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.78"
|
version = "1.0.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -1865,7 +1865,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"smallstr",
|
"smallstr",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1901,7 +1901,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2065,7 +2065,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2313,7 +2313,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlformat",
|
"sqlformat",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2398,7 +2398,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
@ -2438,7 +2438,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
@ -2509,9 +2509,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.48"
|
version = "2.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2578,7 +2578,16 @@ version = "1.0.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 1.0.57",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 2.0.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2589,7 +2598,18 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2673,7 +2693,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2762,7 +2782,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2827,7 +2847,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"rustls",
|
"rustls",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"url",
|
"url",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
@ -2870,7 +2890,7 @@ checksum = "0b122284365ba8497be951b9a21491f70c9688eb6fddc582931a0703f6a00ece"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3021,7 +3041,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3055,7 +3075,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -3369,7 +3389,7 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3392,6 +3412,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
|
"thiserror 2.0.9",
|
||||||
"time",
|
"time",
|
||||||
"youmubot-db",
|
"youmubot-db",
|
||||||
"youmubot-db-sql",
|
"youmubot-db-sql",
|
||||||
|
@ -3412,7 +3433,7 @@ dependencies = [
|
||||||
"poise",
|
"poise",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serenity",
|
"serenity",
|
||||||
"thiserror",
|
"thiserror 1.0.57",
|
||||||
"tokio",
|
"tokio",
|
||||||
"youmubot-db",
|
"youmubot-db",
|
||||||
"youmubot-db-sql",
|
"youmubot-db-sql",
|
||||||
|
@ -3435,7 +3456,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.93",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -24,6 +24,7 @@ poise = "0.6"
|
||||||
zip = "0.6.2"
|
zip = "0.6.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
|
thiserror = "2"
|
||||||
|
|
||||||
youmubot-db = { path = "../youmubot-db" }
|
youmubot-db = { path = "../youmubot-db" }
|
||||||
youmubot-db-sql = { path = "../youmubot-db-sql" }
|
youmubot-db-sql = { path = "../youmubot-db-sql" }
|
||||||
|
|
|
@ -337,11 +337,11 @@ impl<'a> CollectedScore<'a> {
|
||||||
CreateMessage::new()
|
CreateMessage::new()
|
||||||
.content(self.kind.announcement_msg(self.mode, &member))
|
.content(self.kind.announcement_msg(self.mode, &member))
|
||||||
.embed({
|
.embed({
|
||||||
let mut b = score_embed(&self.score, bm, content, self.user);
|
let b = score_embed(&self.score, bm, content, self.user);
|
||||||
let b = if let Some(rank) = self.kind.top_record {
|
let b = if let Some(rank) = self.kind.top_record {
|
||||||
b.top_record(rank)
|
b.top_record(rank)
|
||||||
} else {
|
} else {
|
||||||
&mut b
|
b
|
||||||
};
|
};
|
||||||
let b = if let Some(rank) = self.kind.world_record {
|
let b = if let Some(rank) = self.kind.world_record {
|
||||||
b.world_record(rank)
|
b.world_record(rank)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use display::display_beatmapset;
|
||||||
|
use embeds::ScoreEmbedBuilder;
|
||||||
|
use link_parser::EmbedType;
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use serenity::all::User;
|
use serenity::all::User;
|
||||||
|
|
||||||
/// osu!-related command group.
|
/// osu!-related command group.
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
subcommands("profile", "top", "recent", "pinned", "save", "forcesave")
|
subcommands("profile", "top", "recent", "pinned", "save", "forcesave", "beatmap")
|
||||||
)]
|
)]
|
||||||
pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
|
pub async fn osu<U: HasOsuEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -46,7 +49,7 @@ async fn top<U: HasOsuEnv>(
|
||||||
|
|
||||||
plays.sort_unstable_by(|a, b| b.pp.partial_cmp(&a.pp).unwrap());
|
plays.sort_unstable_by(|a, b| b.pp.partial_cmp(&a.pp).unwrap());
|
||||||
|
|
||||||
handle_listing(ctx, plays, args, "top").await
|
handle_listing(ctx, plays, args, |nth, b| b.top_record(nth), "top").await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an user's profile.
|
/// Get an user's profile.
|
||||||
|
@ -116,7 +119,7 @@ async fn recent<U: HasOsuEnv>(
|
||||||
.user_recent(UserID::ID(args.user.id), |f| f.mode(args.mode).limit(50))
|
.user_recent(UserID::ID(args.user.id), |f| f.mode(args.mode).limit(50))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
handle_listing(ctx, plays, args, "recent").await
|
handle_listing(ctx, plays, args, |_, b| b, "recent").await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns pinned plays from a given player.
|
/// Returns pinned plays from a given player.
|
||||||
|
@ -147,7 +150,7 @@ async fn pinned<U: HasOsuEnv>(
|
||||||
.user_pins(UserID::ID(args.user.id), |f| f.mode(args.mode).limit(50))
|
.user_pins(UserID::ID(args.user.id), |f| f.mode(args.mode).limit(50))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
handle_listing(ctx, plays, args, "pinned").await
|
handle_listing(ctx, plays, args, |_, b| b, "pinned").await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save your osu! profile into Youmu's database for tracking and quick commands.
|
/// Save your osu! profile into Youmu's database for tracking and quick commands.
|
||||||
|
@ -229,6 +232,7 @@ async fn handle_listing<U: HasOsuEnv>(
|
||||||
ctx: CmdContext<'_, U>,
|
ctx: CmdContext<'_, U>,
|
||||||
plays: Vec<Score>,
|
plays: Vec<Score>,
|
||||||
listing_args: ListingArgs,
|
listing_args: ListingArgs,
|
||||||
|
transform: impl for<'a> Fn(u8, ScoreEmbedBuilder<'a>) -> ScoreEmbedBuilder<'a>,
|
||||||
listing_kind: &'static str,
|
listing_kind: &'static str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let env = ctx.data().osu_env();
|
let env = ctx.data().osu_env();
|
||||||
|
@ -257,11 +261,14 @@ async fn handle_listing<U: HasOsuEnv>(
|
||||||
listing_kind,
|
listing_kind,
|
||||||
user.mention()
|
user.mention()
|
||||||
))
|
))
|
||||||
.embed(
|
.embed({
|
||||||
score_embed(&play, &beatmap, &content, user)
|
let mut b =
|
||||||
.top_record(nth + 1)
|
transform(nth + 1, score_embed(&play, &beatmap, &content, user));
|
||||||
.build(),
|
if let Some(rank) = play.global_rank {
|
||||||
)
|
b = b.world_record(rank as u16);
|
||||||
|
}
|
||||||
|
b.build()
|
||||||
|
})
|
||||||
.components(vec![score_components(ctx.guild_id())])
|
.components(vec![score_components(ctx.guild_id())])
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -288,6 +295,130 @@ async fn handle_listing<U: HasOsuEnv>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get information about a beatmap, or the last beatmap mentioned in the channel.
|
||||||
|
#[poise::command(slash_command)]
|
||||||
|
async fn beatmap<U: HasOsuEnv>(
|
||||||
|
ctx: CmdContext<'_, U>,
|
||||||
|
#[description = "A link or shortlink to the beatmap or beatmapset"] map: Option<String>,
|
||||||
|
#[description = "Override the mods on the map"] mods: Option<UnparsedMods>,
|
||||||
|
#[description = "Override the mode of the map"] mode: Option<Mode>,
|
||||||
|
#[description = "Load the beatmapset instead"] beatmapset: Option<bool>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let env = ctx.data().osu_env();
|
||||||
|
|
||||||
|
ctx.defer().await?;
|
||||||
|
|
||||||
|
let beatmap = match map {
|
||||||
|
None => {
|
||||||
|
let Some((BeatmapWithMode(b, mode), bmmods)) =
|
||||||
|
load_beatmap(env, ctx.channel_id(), None as Option<&'_ Message>).await
|
||||||
|
else {
|
||||||
|
return Err(Error::msg("no beatmap mentioned in this channel"));
|
||||||
|
};
|
||||||
|
let mods = bmmods.unwrap_or_else(|| Mods::NOMOD.clone());
|
||||||
|
let info = env
|
||||||
|
.oppai
|
||||||
|
.get_beatmap(b.beatmap_id)
|
||||||
|
.await?
|
||||||
|
.get_possible_pp_with(mode, &mods);
|
||||||
|
EmbedType::Beatmap(Box::new(b), info, mods)
|
||||||
|
}
|
||||||
|
Some(map) => {
|
||||||
|
let Some(results) = stream::select(
|
||||||
|
link_parser::parse_new_links(env, &map),
|
||||||
|
stream::select(
|
||||||
|
link_parser::parse_old_links(env, &map),
|
||||||
|
link_parser::parse_short_links(env, &map),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return Err(Error::msg("no beatmap detected in the argument"));
|
||||||
|
};
|
||||||
|
results.embed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// override into beatmapset if needed
|
||||||
|
let beatmap = if beatmapset == Some(true) {
|
||||||
|
match beatmap {
|
||||||
|
EmbedType::Beatmap(beatmap, _, _) => {
|
||||||
|
let beatmaps = env.beatmaps.get_beatmapset(beatmap.beatmapset_id).await?;
|
||||||
|
EmbedType::Beatmapset(beatmaps)
|
||||||
|
}
|
||||||
|
bm @ EmbedType::Beatmapset(_) => bm,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
beatmap
|
||||||
|
};
|
||||||
|
|
||||||
|
// override mods and mode if needed
|
||||||
|
match beatmap {
|
||||||
|
EmbedType::Beatmap(beatmap, info, bmmods) => {
|
||||||
|
let (beatmap, info, mods) = if mods.is_none() && mode.is_none_or(|v| v == beatmap.mode)
|
||||||
|
{
|
||||||
|
(*beatmap, info, bmmods)
|
||||||
|
} else {
|
||||||
|
let mode = mode.unwrap_or(beatmap.mode);
|
||||||
|
let mods = match mods {
|
||||||
|
None => bmmods,
|
||||||
|
Some(mods) => mods.to_mods(mode)?,
|
||||||
|
};
|
||||||
|
let beatmap = env.beatmaps.get_beatmap(beatmap.beatmap_id, mode).await?;
|
||||||
|
let info = env
|
||||||
|
.oppai
|
||||||
|
.get_beatmap(beatmap.beatmap_id)
|
||||||
|
.await?
|
||||||
|
.get_possible_pp_with(mode, &mods);
|
||||||
|
(beatmap, info, mods)
|
||||||
|
};
|
||||||
|
ctx.send(
|
||||||
|
CreateReply::default()
|
||||||
|
.content(format!(
|
||||||
|
"Information for beatmap `{}`",
|
||||||
|
beatmap.short_link(mode, &mods)
|
||||||
|
))
|
||||||
|
.embed(beatmap_embed(
|
||||||
|
&beatmap,
|
||||||
|
mode.unwrap_or(beatmap.mode),
|
||||||
|
&mods,
|
||||||
|
&info,
|
||||||
|
))
|
||||||
|
.components(vec![beatmap_components(
|
||||||
|
mode.unwrap_or(beatmap.mode),
|
||||||
|
ctx.guild_id(),
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
EmbedType::Beatmapset(vec) => {
|
||||||
|
let b0 = &vec[0];
|
||||||
|
let msg = ctx
|
||||||
|
.clone()
|
||||||
|
.reply(format!(
|
||||||
|
"Information for beatmapset [`/s/{}`](<{}>)",
|
||||||
|
b0.beatmapset_id,
|
||||||
|
b0.beatmapset_link()
|
||||||
|
))
|
||||||
|
.await?
|
||||||
|
.into_message()
|
||||||
|
.await?;
|
||||||
|
display_beatmapset(
|
||||||
|
ctx.serenity_context().clone(),
|
||||||
|
vec,
|
||||||
|
mode,
|
||||||
|
mods,
|
||||||
|
ctx.guild_id(),
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn arg_from_username_or_discord(
|
fn arg_from_username_or_discord(
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
discord_name: Option<User>,
|
discord_name: Option<User>,
|
||||||
|
|
|
@ -346,24 +346,35 @@ mod beatmapset {
|
||||||
|
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
use crate::discord::{interaction::beatmap_components, OsuEnv};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
discord::{cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapWithMode},
|
discord::{cache::save_beatmap, oppai_cache::BeatmapInfoWithPP, BeatmapWithMode},
|
||||||
models::{Beatmap, Mode, Mods},
|
models::{Beatmap, Mode, Mods},
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
discord::{interaction::beatmap_components, OsuEnv},
|
||||||
|
mods::UnparsedMods,
|
||||||
|
};
|
||||||
|
|
||||||
const SHOW_ALL_EMOTE: &str = "🗒️";
|
const SHOW_ALL_EMOTE: &str = "🗒️";
|
||||||
|
|
||||||
pub async fn display_beatmapset(
|
pub async fn display_beatmapset(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
beatmapset: Vec<Beatmap>,
|
mut beatmapset: Vec<Beatmap>,
|
||||||
mode: Option<Mode>,
|
mode: Option<Mode>,
|
||||||
mods: Mods,
|
mods: Option<UnparsedMods>,
|
||||||
guild_id: Option<GuildId>,
|
guild_id: Option<GuildId>,
|
||||||
target: Message,
|
target: Message,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
assert!(!beatmapset.is_empty(), "Beatmapset should not be empty");
|
assert!(!beatmapset.is_empty(), "Beatmapset should not be empty");
|
||||||
|
|
||||||
|
beatmapset.sort_unstable_by(|a, b| {
|
||||||
|
if a.mode != b.mode {
|
||||||
|
(a.mode as u8).cmp(&(b.mode as u8))
|
||||||
|
} else {
|
||||||
|
a.difficulty.stars.partial_cmp(&b.difficulty.stars).unwrap()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let p = Paginate {
|
let p = Paginate {
|
||||||
infos: vec![None; beatmapset.len()],
|
infos: vec![None; beatmapset.len()],
|
||||||
maps: beatmapset,
|
maps: beatmapset,
|
||||||
|
@ -392,20 +403,25 @@ mod beatmapset {
|
||||||
maps: Vec<Beatmap>,
|
maps: Vec<Beatmap>,
|
||||||
infos: Vec<Option<BeatmapInfoWithPP>>,
|
infos: Vec<Option<BeatmapInfoWithPP>>,
|
||||||
mode: Option<Mode>,
|
mode: Option<Mode>,
|
||||||
mods: Mods,
|
mods: Option<UnparsedMods>,
|
||||||
guild_id: Option<GuildId>,
|
guild_id: Option<GuildId>,
|
||||||
|
|
||||||
all_reaction: Option<Reaction>,
|
all_reaction: Option<Reaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paginate {
|
impl Paginate {
|
||||||
async fn get_beatmap_info(&self, ctx: &Context, b: &Beatmap) -> Result<BeatmapInfoWithPP> {
|
async fn get_beatmap_info(
|
||||||
|
&self,
|
||||||
|
ctx: &Context,
|
||||||
|
b: &Beatmap,
|
||||||
|
mods: &Mods,
|
||||||
|
) -> Result<BeatmapInfoWithPP> {
|
||||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||||
|
|
||||||
env.oppai
|
env.oppai
|
||||||
.get_beatmap(b.beatmap_id)
|
.get_beatmap(b.beatmap_id)
|
||||||
.await
|
.await
|
||||||
.map(move |v| v.get_possible_pp_with(self.mode.unwrap_or(b.mode), &self.mods))
|
.map(move |v| v.get_possible_pp_with(b.mode.with_override(self.mode), &mods))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,10 +449,16 @@ mod beatmapset {
|
||||||
}
|
}
|
||||||
|
|
||||||
let map = &self.maps[page];
|
let map = &self.maps[page];
|
||||||
|
let mods = self
|
||||||
|
.mods
|
||||||
|
.clone()
|
||||||
|
.and_then(|v| v.to_mods(map.mode.with_override(self.mode)).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let info = match &self.infos[page] {
|
let info = match &self.infos[page] {
|
||||||
Some(info) => info,
|
Some(info) => info,
|
||||||
None => {
|
None => {
|
||||||
let info = self.get_beatmap_info(ctx, map).await?;
|
let info = self.get_beatmap_info(ctx, map, &mods).await?;
|
||||||
self.infos[page].insert(info)
|
self.infos[page].insert(info)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -445,7 +467,7 @@ mod beatmapset {
|
||||||
crate::discord::embeds::beatmap_embed(
|
crate::discord::embeds::beatmap_embed(
|
||||||
map,
|
map,
|
||||||
self.mode.unwrap_or(map.mode),
|
self.mode.unwrap_or(map.mode),
|
||||||
&self.mods,
|
&mods,
|
||||||
info,
|
info,
|
||||||
)
|
)
|
||||||
.footer({
|
.footer({
|
||||||
|
|
|
@ -270,15 +270,15 @@ pub(crate) struct ScoreEmbedBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ScoreEmbedBuilder<'a> {
|
impl<'a> ScoreEmbedBuilder<'a> {
|
||||||
pub fn top_record(&mut self, rank: u8) -> &mut Self {
|
pub fn top_record(mut self, rank: u8) -> Self {
|
||||||
self.top_record = Some(rank);
|
self.top_record = Some(rank);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn world_record(&mut self, rank: u16) -> &mut Self {
|
pub fn world_record(mut self, rank: u16) -> Self {
|
||||||
self.world_record = Some(rank);
|
self.world_record = Some(rank);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn footer(&mut self, footer: impl Into<String>) -> &mut Self {
|
pub fn footer(mut self, footer: impl Into<String>) -> Self {
|
||||||
self.footer = Some(match self.footer.take() {
|
self.footer = Some(match self.footer.take() {
|
||||||
None => footer.into(),
|
None => footer.into(),
|
||||||
Some(pre) => format!("{} | {}", pre, footer.into()),
|
Some(pre) => format!("{} | {}", pre, footer.into()),
|
||||||
|
@ -306,7 +306,7 @@ pub(crate) fn score_embed<'a>(
|
||||||
|
|
||||||
impl<'a> ScoreEmbedBuilder<'a> {
|
impl<'a> ScoreEmbedBuilder<'a> {
|
||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
pub fn build(&mut self) -> CreateEmbed {
|
pub fn build(mut self) -> CreateEmbed {
|
||||||
let mode = self.bm.mode();
|
let mode = self.bm.mode();
|
||||||
let b = &self.bm.0;
|
let b = &self.bm.0;
|
||||||
let s = self.s;
|
let s = self.s;
|
||||||
|
|
|
@ -323,7 +323,7 @@ async fn handle_beatmapset<'a, 'b>(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
beatmaps,
|
beatmaps,
|
||||||
mode,
|
mode,
|
||||||
Mods::default(),
|
None,
|
||||||
reply_to.guild_id,
|
reply_to.guild_id,
|
||||||
reply,
|
reply,
|
||||||
)
|
)
|
||||||
|
|
|
@ -325,7 +325,7 @@ async fn handle_last_req(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
beatmapset,
|
beatmapset,
|
||||||
None,
|
None,
|
||||||
mods,
|
None,
|
||||||
comp.guild_id,
|
comp.guild_id,
|
||||||
reply,
|
reply,
|
||||||
)
|
)
|
||||||
|
|
|
@ -859,10 +859,7 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
|
|
||||||
match b {
|
match b {
|
||||||
Some((bm, mods_def)) => {
|
Some((bm, mods_def)) => {
|
||||||
let mods = match args.find::<UnparsedMods>().ok() {
|
let mods = args.find::<UnparsedMods>().ok();
|
||||||
Some(m) => m.to_mods(bm.mode())?,
|
|
||||||
None => mods_def.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
if beatmapset {
|
if beatmapset {
|
||||||
let beatmapset = env.beatmaps.get_beatmapset(bm.0.beatmapset_id).await?;
|
let beatmapset = env.beatmaps.get_beatmapset(bm.0.beatmapset_id).await?;
|
||||||
let reply = msg
|
let reply = msg
|
||||||
|
@ -879,6 +876,10 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let mods = match mods {
|
||||||
|
Some(m) => m.to_mods(bm.mode())?,
|
||||||
|
None => mods_def.unwrap_or_default(),
|
||||||
|
};
|
||||||
let info = env
|
let info = env
|
||||||
.oppai
|
.oppai
|
||||||
.get_beatmap(bm.0.beatmap_id)
|
.get_beatmap(bm.0.beatmap_id)
|
||||||
|
|
|
@ -341,6 +341,14 @@ impl Mode {
|
||||||
Mode::Mania => "mania",
|
Mode::Mania => "mania",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_override(self, opt_mode: impl Into<Option<Mode>>) -> Self {
|
||||||
|
if self == Mode::Std {
|
||||||
|
opt_mode.into().unwrap_or(self)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
|
|
@ -27,8 +27,16 @@ pub struct UnparsedMods {
|
||||||
clock: Option<f64>,
|
clock: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum ModParseError {
|
||||||
|
#[error("invalid mods `{0}`")]
|
||||||
|
Invalid(String),
|
||||||
|
#[error("not a mod: `{0}`")]
|
||||||
|
NotAMod(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for UnparsedMods {
|
impl FromStr for UnparsedMods {
|
||||||
type Err = String;
|
type Err = ModParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
|
@ -36,12 +44,12 @@ impl FromStr for UnparsedMods {
|
||||||
}
|
}
|
||||||
let ms = match MODS.captures(s) {
|
let ms = match MODS.captures(s) {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => return Err(format!("invalid mods: {}", s)),
|
None => return Err(ModParseError::Invalid(s.to_owned())),
|
||||||
};
|
};
|
||||||
let mods = ms.name("mods").map(|v| v.as_str().to_owned());
|
let mods = ms.name("mods").map(|v| v.as_str().to_owned());
|
||||||
if let Some(mods) = &mods {
|
if let Some(mods) = &mods {
|
||||||
if GameModsIntermode::try_from_acronyms(mods).is_none() {
|
if GameModsIntermode::try_from_acronyms(mods).is_none() {
|
||||||
return Err(format!("invalid mod sequence: {}", mods));
|
return Err(ModParseError::NotAMod(mods.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let is_lazer = ms.name("lazer").is_some();
|
let is_lazer = ms.name("lazer").is_some();
|
||||||
|
|
Loading…
Add table
Reference in a new issue