mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 16:28:55 +00:00
osu: make commands aware of user's preferred mode (#54)
* Add preferred_mode to sql database * Update username and preferred mode * Make commands aware of preferred mode * Fetch user extras to display information * Show user information on forcesave
This commit is contained in:
parent
c5354e30ad
commit
7d490774e0
37 changed files with 410 additions and 170 deletions
44
.sqlx/query-25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366.json
generated
Normal file
44
.sqlx/query-25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366.json
generated
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users WHERE user_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user_id: i64",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "id: i64",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n failures as \"failures: u8\"\n FROM osu_users",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -19,9 +19,14 @@
|
|||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -31,8 +36,9 @@
|
|||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "246e26a34c042872a77f53a84d62da31db069cced20e3b0f96a40c3c7dd99783"
|
||||
"hash": "54f54f669244fbdf1ad68664290d8f32f0bda74ceee62d10c84ac03b710c828c"
|
||||
}
|
12
.sqlx/query-b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378.json
generated
Normal file
12
.sqlx/query-b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378.json
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT\n INTO osu_users(user_id, username, id, preferred_mode, failures)\n VALUES(?, ?, ?, ?, ?)\n ON CONFLICT (user_id) WHERE id = ? DO UPDATE\n SET\n username = excluded.username,\n preferred_mode = excluded.preferred_mode,\n failures = excluded.failures\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n failures as \"failures: u8\"\n FROM osu_users WHERE id = ?",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -19,9 +19,14 @@
|
|||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -31,8 +36,9 @@
|
|||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9b7788f4d7144fe00f4bc9004c88dc8562ff3d7a931fc3f1dc039cc55fe3195a"
|
||||
"hash": "e313b2e91d0da80a94e9a1030f94cf30b025c9ac0ac4d5bcc12656459c5e083a"
|
||||
}
|
44
youmubot-db-sql/.sqlx/query-25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366.json
generated
Normal file
44
youmubot-db-sql/.sqlx/query-25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366.json
generated
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users WHERE user_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user_id: i64",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "id: i64",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "25dcc59341e6375ee6a55aa014aecc54be42e1e8787ae5a06a61bb8ba8e9c366"
|
||||
}
|
44
youmubot-db-sql/.sqlx/query-54f54f669244fbdf1ad68664290d8f32f0bda74ceee62d10c84ac03b710c828c.json
generated
Normal file
44
youmubot-db-sql/.sqlx/query-54f54f669244fbdf1ad68664290d8f32f0bda74ceee62d10c84ac03b710c828c.json
generated
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "user_id: i64",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "id: i64",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "54f54f669244fbdf1ad68664290d8f32f0bda74ceee62d10c84ac03b710c828c"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT\n INTO osu_users(user_id, username, id, failures)\n VALUES(?, ?, ?, ?)\n ON CONFLICT (user_id) WHERE id = ? DO UPDATE\n SET\n username = excluded.username,\n failures = excluded.failures\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a5d8dccaaf80b2673c5c0e689c01a90861788ca84221baaaf19cd159ed3062c9"
|
||||
}
|
12
youmubot-db-sql/.sqlx/query-b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378.json
generated
Normal file
12
youmubot-db-sql/.sqlx/query-b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378.json
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT\n INTO osu_users(user_id, username, id, preferred_mode, failures)\n VALUES(?, ?, ?, ?, ?)\n ON CONFLICT (user_id) WHERE id = ? DO UPDATE\n SET\n username = excluded.username,\n preferred_mode = excluded.preferred_mode,\n failures = excluded.failures\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b9c63ef764711088cfbb58ce2ed1f46e3521357ec1d7c062bef762d3e4267378"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n failures as \"failures: u8\"\n FROM osu_users WHERE user_id = ?",
|
||||
"query": "SELECT\n user_id as \"user_id: i64\",\n username,\n id as \"id: i64\",\n preferred_mode as \"preferred_mode: u8\",\n failures as \"failures: u8\"\n FROM osu_users WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -19,9 +19,14 @@
|
|||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"name": "preferred_mode: u8",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failures: u8",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
@ -31,8 +36,9 @@
|
|||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ae7f57eb92e0bec8439e682ab3ca10732991ffe803d05b09e908ecb4a74c0566"
|
||||
"hash": "e313b2e91d0da80a94e9a1030f94cf30b025c9ac0ac4d5bcc12656459c5e083a"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
-- Add migration script here
|
||||
|
||||
ALTER TABLE osu_users
|
||||
ADD COLUMN preferred_mode INT NOT NULL DEFAULT 0 CHECK (preferred_mode >= 0 AND preferred_mode < 4);
|
|
@ -9,6 +9,7 @@ pub struct OsuUser {
|
|||
pub username: Option<String>, // should always be there
|
||||
pub id: i64,
|
||||
pub modes: Map<u8, OsuUserMode>,
|
||||
pub preferred_mode: u8,
|
||||
/// Number of consecutive update failures
|
||||
pub failures: u8,
|
||||
}
|
||||
|
@ -97,6 +98,7 @@ mod raw {
|
|||
pub user_id: i64,
|
||||
pub username: Option<String>, // should always be there
|
||||
pub id: i64,
|
||||
pub preferred_mode: u8,
|
||||
pub failures: u8,
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +110,7 @@ impl OsuUser {
|
|||
username: r.username,
|
||||
id: r.id,
|
||||
modes,
|
||||
preferred_mode: r.preferred_mode,
|
||||
failures: r.failures,
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +122,7 @@ impl OsuUser {
|
|||
user_id as "user_id: i64",
|
||||
username,
|
||||
id as "id: i64",
|
||||
preferred_mode as "preferred_mode: u8",
|
||||
failures as "failures: u8"
|
||||
FROM osu_users WHERE user_id = ?"#,
|
||||
user_id
|
||||
|
@ -141,6 +145,7 @@ impl OsuUser {
|
|||
user_id as "user_id: i64",
|
||||
username,
|
||||
id as "id: i64",
|
||||
preferred_mode as "preferred_mode: u8",
|
||||
failures as "failures: u8"
|
||||
FROM osu_users WHERE id = ?"#,
|
||||
osu_id
|
||||
|
@ -164,6 +169,7 @@ impl OsuUser {
|
|||
user_id as "user_id: i64",
|
||||
username,
|
||||
id as "id: i64",
|
||||
preferred_mode as "preferred_mode: u8",
|
||||
failures as "failures: u8"
|
||||
FROM osu_users"#,
|
||||
)
|
||||
|
@ -200,16 +206,18 @@ impl OsuUser {
|
|||
|
||||
query!(
|
||||
r#"INSERT
|
||||
INTO osu_users(user_id, username, id, failures)
|
||||
VALUES(?, ?, ?, ?)
|
||||
INTO osu_users(user_id, username, id, preferred_mode, failures)
|
||||
VALUES(?, ?, ?, ?, ?)
|
||||
ON CONFLICT (user_id) WHERE id = ? DO UPDATE
|
||||
SET
|
||||
username = excluded.username,
|
||||
preferred_mode = excluded.preferred_mode,
|
||||
failures = excluded.failures
|
||||
"#,
|
||||
self.user_id,
|
||||
self.username,
|
||||
self.id,
|
||||
self.preferred_mode,
|
||||
self.failures,
|
||||
self.user_id,
|
||||
)
|
||||
|
|
|
@ -122,6 +122,10 @@ impl Announcer {
|
|||
.unwrap_or(0),
|
||||
last_update: now,
|
||||
};
|
||||
if u.username != user.username {
|
||||
user.username = u.username.clone().into();
|
||||
}
|
||||
user.preferred_mode = u.preferred_mode;
|
||||
let last = user.modes.insert(mode, stats);
|
||||
|
||||
// broadcast
|
||||
|
|
|
@ -113,6 +113,7 @@ pub struct OsuUser {
|
|||
pub username: Cow<'static, str>,
|
||||
pub id: u64,
|
||||
pub modes: Map<Mode, OsuUserMode>,
|
||||
pub preferred_mode: Mode,
|
||||
/// More than 5 failures => gone
|
||||
pub failures: u8,
|
||||
}
|
||||
|
@ -136,6 +137,7 @@ impl From<OsuUser> for model::OsuUser {
|
|||
.into_iter()
|
||||
.map(|(k, v)| (k as u8, v.into()))
|
||||
.collect(),
|
||||
preferred_mode: u.preferred_mode as u8,
|
||||
failures: u.failures,
|
||||
}
|
||||
}
|
||||
|
@ -152,11 +154,21 @@ impl From<model::OsuUser> for OsuUser {
|
|||
.into_iter()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.collect(),
|
||||
preferred_mode: u.preferred_mode.into(),
|
||||
failures: u.failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsuUser> for crate::models::UserHeader {
|
||||
fn from(value: OsuUser) -> Self {
|
||||
Self {
|
||||
id: value.id as u64,
|
||||
username: value.username.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsuUserMode> for model::OsuUserMode {
|
||||
fn from(m: OsuUserMode) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::BeatmapWithMode;
|
||||
use super::{BeatmapWithMode, UserExtras};
|
||||
use crate::{
|
||||
discord::oppai_cache::{Accuracy, BeatmapContent, BeatmapInfo, BeatmapInfoWithPP},
|
||||
discord::oppai_cache::{Accuracy, BeatmapContent, BeatmapInfoWithPP},
|
||||
models::{Beatmap, Difficulty, Mode, Mods, Rank, Score, User},
|
||||
UserHeader,
|
||||
};
|
||||
|
@ -479,13 +479,13 @@ impl<'a> ScoreEmbedBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn user_embed(
|
||||
u: User,
|
||||
map_length: f64,
|
||||
map_age: i64,
|
||||
best: Option<(Score, BeatmapWithMode, BeatmapInfo)>,
|
||||
) -> CreateEmbed {
|
||||
pub(crate) fn user_embed(u: User, ex: UserExtras) -> CreateEmbed {
|
||||
let mut stats = Vec::<(&'static str, String, bool)>::new();
|
||||
let UserExtras {
|
||||
map_length,
|
||||
map_age,
|
||||
best_score: best,
|
||||
} = ex;
|
||||
if map_length > 0.0 {
|
||||
stats.push((
|
||||
"Weighted Map Length",
|
||||
|
|
|
@ -8,7 +8,7 @@ use serenity::all::{
|
|||
};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
use crate::Mods;
|
||||
use crate::{Mods, UserHeader};
|
||||
|
||||
use super::{
|
||||
display::ScoreListStyle,
|
||||
|
@ -70,8 +70,9 @@ pub fn handle_check_button<'a>(
|
|||
return Ok(());
|
||||
}
|
||||
};
|
||||
let header = UserHeader::from(user.clone());
|
||||
|
||||
let scores = super::do_check(&env, &bm, Mods::NOMOD, &crate::UserID::ID(user.id)).await?;
|
||||
let scores = super::do_check(&env, &bm, Mods::NOMOD, &header).await?;
|
||||
if scores.is_empty() {
|
||||
comp.create_followup(
|
||||
&ctx,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::{borrow::Borrow, collections::HashMap as Map, str::FromStr, sync::Arc};
|
||||
|
||||
use chrono::Utc;
|
||||
use future::try_join;
|
||||
use futures_util::join;
|
||||
use interaction::{beatmap_components, score_components};
|
||||
use rand::seq::IteratorRandom;
|
||||
|
@ -33,7 +32,7 @@ use crate::{
|
|||
models::{Beatmap, Mode, Mods, Score, User},
|
||||
mods::UnparsedMods,
|
||||
request::{BeatmapRequestKind, UserID},
|
||||
OsuClient as OsuHttpClient,
|
||||
OsuClient as OsuHttpClient, UserHeader,
|
||||
};
|
||||
|
||||
mod announcer;
|
||||
|
@ -141,6 +140,7 @@ pub async fn setup(
|
|||
#[prefix = "osu"]
|
||||
#[description = "osu! related commands."]
|
||||
#[commands(
|
||||
user,
|
||||
std,
|
||||
taiko,
|
||||
catch,
|
||||
|
@ -156,9 +156,19 @@ pub async fn setup(
|
|||
show_leaderboard,
|
||||
clean_cache
|
||||
)]
|
||||
#[default_command(std)]
|
||||
#[default_command(user)]
|
||||
|
||||
struct Osu;
|
||||
|
||||
#[command]
|
||||
#[description = "Receive information about an user in their preferred mode."]
|
||||
#[usage = "[username or user_id = your saved username]"]
|
||||
#[max_args(1)]
|
||||
pub async fn user(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
let env = ctx.data.read().await.get::<OsuEnv>().unwrap().clone();
|
||||
get_user(ctx, &env, msg, args, None).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[aliases("osu", "osu!")]
|
||||
#[description = "Receive information about an user in osu!std mode."]
|
||||
|
@ -235,7 +245,13 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
}
|
||||
};
|
||||
async fn find_score(client: &OsuHttpClient, u: &User) -> Result<Option<(Score, Mode)>> {
|
||||
for mode in &[Mode::Std, Mode::Taiko, Mode::Catch, Mode::Mania] {
|
||||
for mode in &[
|
||||
u.preferred_mode,
|
||||
Mode::Std,
|
||||
Mode::Taiko,
|
||||
Mode::Catch,
|
||||
Mode::Mania,
|
||||
] {
|
||||
let scores = client
|
||||
.user_best(UserID::ID(u.id), |f| f.mode(*mode))
|
||||
.await?;
|
||||
|
@ -269,10 +285,11 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
let reply = msg.reply(
|
||||
&ctx,
|
||||
format!(
|
||||
"To set your osu username, please make your most recent play \
|
||||
"To set your osu username to **{}**, please make your most recent play \
|
||||
be the following map: `/b/{}` in **{}** mode! \
|
||||
It does **not** have to be a pass, and **NF** can be used! \
|
||||
React to this message with 👌 within 5 minutes when you're done!",
|
||||
u.username,
|
||||
score.beatmap_id,
|
||||
mode.as_str_new_site()
|
||||
),
|
||||
|
@ -306,7 +323,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
.message_id(reply.id)
|
||||
.author_id(msg.author.id)
|
||||
.filter(move |r| r.emoji == emoji)
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.timeout(std::time::Duration::from_secs(300) + beatmap.difficulty.total_length)
|
||||
.next()
|
||||
.await;
|
||||
if let Some(ur) = user_reaction {
|
||||
|
@ -319,20 +336,41 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
}
|
||||
};
|
||||
if !completed {
|
||||
reply
|
||||
.edit(
|
||||
&ctx,
|
||||
EditMessage::new()
|
||||
.content(format!(
|
||||
"Setting username to **{}** failed due to timeout. Please try again!",
|
||||
u.username
|
||||
))
|
||||
.embeds(vec![])
|
||||
.components(vec![]),
|
||||
)
|
||||
.await?;
|
||||
reaction.delete(&ctx).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let username = u.username.clone();
|
||||
add_user(msg.author.id, u, &env).await?;
|
||||
msg.reply(
|
||||
&ctx,
|
||||
MessageBuilder::new()
|
||||
.push("user has been set to ")
|
||||
.push_mono_safe(username)
|
||||
.build(),
|
||||
)
|
||||
.await?;
|
||||
add_user(msg.author.id, &u, &env).await?;
|
||||
let ex = UserExtras::from_user(&env, &u, mode).await?;
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.reference_message(msg)
|
||||
.content(
|
||||
MessageBuilder::new()
|
||||
.push("Youmu is now tracking user ")
|
||||
.push(msg.author.mention().to_string())
|
||||
.push(" with osu! account ")
|
||||
.push_bold_safe(username)
|
||||
.build(),
|
||||
)
|
||||
.add_embed(user_embed(u, ex)),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -355,15 +393,24 @@ pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandR
|
|||
.await?;
|
||||
match user {
|
||||
Some(u) => {
|
||||
add_user(target, u, &env).await?;
|
||||
msg.reply(
|
||||
&ctx,
|
||||
MessageBuilder::new()
|
||||
.push("user has been set to ")
|
||||
.push_mono_safe(username)
|
||||
.build(),
|
||||
)
|
||||
.await?;
|
||||
add_user(target, &u, &env).await?;
|
||||
let ex = UserExtras::from_user(&env, &u, u.preferred_mode).await?;
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
CreateMessage::new()
|
||||
.reference_message(msg)
|
||||
.content(
|
||||
MessageBuilder::new()
|
||||
.push("Youmu is now tracking user ")
|
||||
.push(target.mention().to_string())
|
||||
.push(" with osu! account ")
|
||||
.push_bold_safe(username)
|
||||
.build(),
|
||||
)
|
||||
.embed(user_embed(u, ex)),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
None => {
|
||||
msg.reply(&ctx, "user not found...").await?;
|
||||
|
@ -372,49 +419,34 @@ pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandR
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv) -> Result<()> {
|
||||
async fn add_user(target: serenity::model::id::UserId, user: &User, env: &OsuEnv) -> Result<()> {
|
||||
let modes = [Mode::Std, Mode::Taiko, Mode::Catch, Mode::Mania]
|
||||
.into_iter()
|
||||
.map(|mode| async move {
|
||||
let pp = async {
|
||||
env.client
|
||||
.user(&UserID::ID(user.id), |f| f.mode(mode))
|
||||
.await
|
||||
.pls_ok()
|
||||
.unwrap_or(None)
|
||||
.and_then(|u| u.pp)
|
||||
};
|
||||
let map_length_age = async {
|
||||
let scores = env
|
||||
.client
|
||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
.await
|
||||
.pls_ok()
|
||||
.unwrap_or_else(std::vec::Vec::new);
|
||||
|
||||
(
|
||||
calculate_weighted_map_length(&scores, &env.beatmaps, mode)
|
||||
.map(|mode| {
|
||||
let mode = mode.clone();
|
||||
async move {
|
||||
let pp = async {
|
||||
env.client
|
||||
.user(&UserID::ID(user.id), |f| f.mode(mode))
|
||||
.await
|
||||
.pls_ok(),
|
||||
calculate_weighted_map_age(&scores, &env.beatmaps, mode)
|
||||
.await
|
||||
.pls_ok(),
|
||||
)
|
||||
};
|
||||
let (pp, (map_length, map_age)) = join!(pp, map_length_age);
|
||||
pp.zip(map_length)
|
||||
.zip(map_age)
|
||||
.map(|((pp, map_length), map_age)| {
|
||||
.pls_ok()
|
||||
.unwrap_or(None)
|
||||
.and_then(|u| u.pp)
|
||||
};
|
||||
let map_length_age = UserExtras::from_user(env, user, mode);
|
||||
let (pp, ex) = join!(pp, map_length_age);
|
||||
pp.zip(ex.ok()).map(|(pp, ex)| {
|
||||
(
|
||||
mode,
|
||||
OsuUserMode {
|
||||
pp,
|
||||
map_length,
|
||||
map_age,
|
||||
map_length: ex.map_length,
|
||||
map_age: ex.map_age,
|
||||
last_update: Utc::now(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<stream::FuturesOrdered<_>>()
|
||||
.filter_map(future::ready)
|
||||
|
@ -423,7 +455,8 @@ async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv)
|
|||
|
||||
let u = OsuUser {
|
||||
user_id: target,
|
||||
username: user.username.into(),
|
||||
username: user.username.clone().into(),
|
||||
preferred_mode: user.preferred_mode,
|
||||
id: user.id,
|
||||
failures: 0,
|
||||
modes,
|
||||
|
@ -432,6 +465,47 @@ async fn add_user(target: serenity::model::id::UserId, user: User, env: &OsuEnv)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores extra information to create an user embed.
|
||||
pub(crate) struct UserExtras {
|
||||
pub map_length: f64,
|
||||
pub map_age: i64,
|
||||
pub best_score: Option<(Score, BeatmapWithMode, BeatmapInfo)>,
|
||||
}
|
||||
|
||||
impl UserExtras {
|
||||
// Collect UserExtras from the given user.
|
||||
pub async fn from_user(env: &OsuEnv, user: &User, mode: Mode) -> Result<Self> {
|
||||
let scores = env
|
||||
.client
|
||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
.await
|
||||
.pls_ok()
|
||||
.unwrap_or_else(std::vec::Vec::new);
|
||||
|
||||
let (length, age) = join!(
|
||||
calculate_weighted_map_length(&scores, &env.beatmaps, mode),
|
||||
calculate_weighted_map_age(&scores, &env.beatmaps, mode)
|
||||
);
|
||||
let best = if let Some(s) = scores.into_iter().next() {
|
||||
let beatmap = env.beatmaps.get_beatmap(s.beatmap_id, mode).await?;
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(s.beatmap_id)
|
||||
.await?
|
||||
.get_info_with(mode, &s.mods)?;
|
||||
Some((s, BeatmapWithMode(beatmap, mode), info))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
map_length: length.unwrap_or(0.0),
|
||||
map_age: age.unwrap_or(0),
|
||||
best_score: best,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ModeArg(Mode);
|
||||
|
||||
|
@ -448,24 +522,6 @@ impl FromStr for ModeArg {
|
|||
}
|
||||
}
|
||||
|
||||
async fn to_user_id_query(
|
||||
s: Option<UsernameArg>,
|
||||
env: &OsuEnv,
|
||||
author: serenity::all::UserId,
|
||||
) -> Result<UserID, Error> {
|
||||
let id = match s {
|
||||
Some(UsernameArg::Raw(s)) => return Ok(UserID::from_string(s)),
|
||||
Some(UsernameArg::Tagged(r)) => r,
|
||||
None => author,
|
||||
};
|
||||
|
||||
env.saved_users
|
||||
.by_user_id(id)
|
||||
.await?
|
||||
.map(|u| UserID::Username(u.username.to_string()))
|
||||
.ok_or_else(|| Error::msg("No saved account found"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
enum Nth {
|
||||
#[default]
|
||||
|
@ -496,7 +552,7 @@ struct ListingArgs {
|
|||
pub nth: Nth,
|
||||
pub style: ScoreListStyle,
|
||||
pub mode: Mode,
|
||||
pub user: UserID,
|
||||
pub user: UserHeader,
|
||||
}
|
||||
|
||||
impl ListingArgs {
|
||||
|
@ -508,13 +564,10 @@ impl ListingArgs {
|
|||
) -> Result<ListingArgs> {
|
||||
let nth = args.single::<Nth>().unwrap_or(Nth::All);
|
||||
let style = args.single::<ScoreListStyle>().unwrap_or(default_style);
|
||||
let mode = args.single::<ModeArg>().unwrap_or(ModeArg(Mode::Std)).0;
|
||||
let user = to_user_id_query(
|
||||
args.quoted().trimmed().single::<UsernameArg>().ok(),
|
||||
&env,
|
||||
msg.author.id,
|
||||
)
|
||||
.await?;
|
||||
let mode_override = args.single::<ModeArg>().map(|v| v.0).ok();
|
||||
let (mode, user) =
|
||||
user_header_from_args(args.single::<UsernameArg>().ok(), env, msg).await?;
|
||||
let mode = mode_override.unwrap_or(mode);
|
||||
Ok(Self {
|
||||
nth,
|
||||
style,
|
||||
|
@ -524,6 +577,35 @@ impl ListingArgs {
|
|||
}
|
||||
}
|
||||
|
||||
async fn user_header_from_args(
|
||||
arg: Option<UsernameArg>,
|
||||
env: &OsuEnv,
|
||||
msg: &Message,
|
||||
) -> Result<(Mode, UserHeader)> {
|
||||
let (mode, user) = match arg {
|
||||
Some(UsernameArg::Raw(r)) => {
|
||||
let user = env
|
||||
.client
|
||||
.user(&UserID::Username(r), |f| f)
|
||||
.await?
|
||||
.ok_or(Error::msg("User not found"))?;
|
||||
(user.preferred_mode, user.into())
|
||||
}
|
||||
Some(UsernameArg::Tagged(t)) => {
|
||||
let user = env.saved_users.by_user_id(t).await?.ok_or_else(|| {
|
||||
Error::msg(format!("{} does not have a saved account!", t.mention()))
|
||||
})?;
|
||||
(user.preferred_mode, user.into())
|
||||
}
|
||||
None => {
|
||||
let user = env.saved_users.by_user_id(msg.author.id).await?
|
||||
.ok_or(Error::msg("You do not have a saved account! Use `osu save` command to save your osu! account."))?;
|
||||
(user.preferred_mode, user.into())
|
||||
}
|
||||
};
|
||||
Ok((mode, user))
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[aliases("rs", "rc", "r")]
|
||||
#[description = "Gets an user's recent play"]
|
||||
|
@ -542,11 +624,6 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::Table).await?;
|
||||
|
||||
let osu_client = &env.client;
|
||||
|
||||
let user = osu_client
|
||||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
let plays = osu_client
|
||||
.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
|
@ -581,7 +658,7 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
CreateMessage::new()
|
||||
.content("Here is the play that you requested".to_string())
|
||||
.embed(
|
||||
score_embed(play, &beatmap_mode, &content, &user)
|
||||
score_embed(play, &beatmap_mode, &content, user)
|
||||
.footer(format!("Attempt #{}", attempts))
|
||||
.build(),
|
||||
)
|
||||
|
@ -616,10 +693,6 @@ pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
|
||||
let osu_client = &env.client;
|
||||
|
||||
let user = osu_client
|
||||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
let plays = osu_client
|
||||
.user_pins(UserID::ID(user.id), |f| f.mode(mode).limit(50))
|
||||
.await?;
|
||||
|
@ -648,7 +721,7 @@ pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
&ctx,
|
||||
CreateMessage::new()
|
||||
.content("Here is the play that you requested".to_string())
|
||||
.embed(score_embed(play, &beatmap_mode, &content, &user).build())
|
||||
.embed(score_embed(play, &beatmap_mode, &content, user).build())
|
||||
.components(vec![score_components(msg.guild_id)])
|
||||
.reference_message(msg),
|
||||
)
|
||||
|
@ -803,7 +876,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
.single::<ScoreListStyle>()
|
||||
.unwrap_or(ScoreListStyle::Grid);
|
||||
let username_arg = args.single::<UsernameArg>().ok();
|
||||
let user = to_user_id_query(username_arg, &env, msg.author.id).await?;
|
||||
let (_, user) = user_header_from_args(username_arg, &env, msg).await?;
|
||||
|
||||
let scores = do_check(&env, &bm, &mods, &user).await?;
|
||||
|
||||
|
@ -816,7 +889,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
|
|||
&ctx,
|
||||
format!(
|
||||
"Here are the scores by `{}` on `{}`!",
|
||||
&user,
|
||||
&user.username,
|
||||
bm.short_link(&mods)
|
||||
),
|
||||
)
|
||||
|
@ -832,16 +905,12 @@ pub(crate) async fn do_check(
|
|||
env: &OsuEnv,
|
||||
bm: &BeatmapWithMode,
|
||||
mods: &Mods,
|
||||
user: &UserID,
|
||||
user: &UserHeader,
|
||||
) -> Result<Vec<Score>> {
|
||||
let BeatmapWithMode(b, m) = bm;
|
||||
|
||||
let osu_client = &env.client;
|
||||
|
||||
let user = osu_client
|
||||
.user(user, |f| f)
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
let mut scores = osu_client
|
||||
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(*m))
|
||||
.await?
|
||||
|
@ -871,10 +940,6 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
user,
|
||||
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::default()).await?;
|
||||
let osu_client = &env.client;
|
||||
let user = osu_client
|
||||
.user(&user, |f| f.mode(mode))
|
||||
.await?
|
||||
.ok_or_else(|| Error::msg("User not found"))?;
|
||||
|
||||
let plays = osu_client
|
||||
.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))
|
||||
|
@ -898,7 +963,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
msg.author
|
||||
))
|
||||
.embed(
|
||||
score_embed(&play, &beatmap, &content, &user)
|
||||
score_embed(&play, &beatmap, &content, user)
|
||||
.top_record(nth + 1)
|
||||
.build(),
|
||||
)
|
||||
|
@ -945,33 +1010,18 @@ async fn get_user(
|
|||
env: &OsuEnv,
|
||||
msg: &Message,
|
||||
mut args: Args,
|
||||
mode: Mode,
|
||||
mode_override: impl Into<Option<Mode>>,
|
||||
) -> CommandResult {
|
||||
let user = to_user_id_query(args.single::<UsernameArg>().ok(), env, msg.author.id).await?;
|
||||
let osu_client = &env.client;
|
||||
let meta_cache = &env.beatmaps;
|
||||
let user = osu_client.user(&user, |f| f.mode(mode)).await?;
|
||||
let (mode, user) = user_header_from_args(args.single::<UsernameArg>().ok(), env, msg).await?;
|
||||
let mode = mode_override.into().unwrap_or(mode);
|
||||
let user = env
|
||||
.client
|
||||
.user(&UserID::ID(user.id), |f| f.mode(mode))
|
||||
.await?;
|
||||
|
||||
match user {
|
||||
Some(u) => {
|
||||
let bests = osu_client
|
||||
.user_best(UserID::ID(u.id), |f| f.limit(100).mode(mode))
|
||||
.await?;
|
||||
let map_length = calculate_weighted_map_length(&bests, meta_cache, mode);
|
||||
let map_age = calculate_weighted_map_age(&bests, meta_cache, mode);
|
||||
let (map_length, map_age) = try_join(map_length, map_age).await?;
|
||||
let best = match bests.into_iter().next() {
|
||||
Some(m) => {
|
||||
let beatmap = meta_cache.get_beatmap(m.beatmap_id, mode).await?;
|
||||
let info = env
|
||||
.oppai
|
||||
.get_beatmap(m.beatmap_id)
|
||||
.await?
|
||||
.get_info_with(mode, &m.mods)?;
|
||||
Some((m, BeatmapWithMode(beatmap, mode), info))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let ex = UserExtras::from_user(env, &u, mode).await?;
|
||||
msg.channel_id
|
||||
.send_message(
|
||||
&ctx,
|
||||
|
@ -980,7 +1030,7 @@ async fn get_user(
|
|||
"{}: here is the user that you requested",
|
||||
msg.author
|
||||
))
|
||||
.embed(user_embed(u, map_length, map_age, best)),
|
||||
.embed(user_embed(u, ex)),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
|
|
@ -488,7 +488,6 @@ impl UserEvent {
|
|||
pub struct UserHeader {
|
||||
pub id: u64,
|
||||
pub username: String,
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -497,6 +496,7 @@ pub struct User {
|
|||
pub username: String,
|
||||
pub joined: DateTime<Utc>,
|
||||
pub country: String,
|
||||
pub preferred_mode: Mode,
|
||||
// History
|
||||
pub count_300: u64,
|
||||
pub count_100: u64,
|
||||
|
@ -544,7 +544,6 @@ impl<'a> From<&'a User> for UserHeader {
|
|||
Self {
|
||||
id: u.id,
|
||||
username: u.username.clone(),
|
||||
country: u.country.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -554,7 +553,6 @@ impl From<User> for UserHeader {
|
|||
Self {
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
country: u.country,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ impl User {
|
|||
username: user.username.into_string(),
|
||||
joined: time_to_utc(user.join_date),
|
||||
country: user.country_code.to_string(),
|
||||
preferred_mode: user.mode.into(),
|
||||
count_300: 0, // why do we even want this
|
||||
count_100: 0, // why do we even want this
|
||||
count_50: 0, // why do we even want this
|
||||
|
|
|
@ -132,8 +132,8 @@ pub mod builders {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mode(&mut self, mode: Mode) -> &mut Self {
|
||||
self.mode = Some(mode);
|
||||
pub fn mode(&mut self, mode: impl Into<Option<Mode>>) -> &mut Self {
|
||||
self.mode = mode.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -185,8 +185,8 @@ pub mod builders {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn mode(&mut self, mode: Mode) -> &mut Self {
|
||||
self.mode = Some(mode);
|
||||
pub fn mode(&mut self, mode: impl Into<Option<Mode>>) -> &mut Self {
|
||||
self.mode = mode.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue