Chart info command
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
8339ce7054
commit
373e54c55e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ data/songs
|
||||||
backups
|
backups
|
||||||
dump.sql
|
dump.sql
|
||||||
logs
|
logs
|
||||||
|
cache
|
||||||
|
|
142
Cargo.lock
generated
142
Cargo.lock
generated
|
@ -118,6 +118,15 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-polyfill"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -373,6 +382,12 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color_quant"
|
name = "color_quant"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -470,6 +485,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
|
@ -581,7 +602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
|
@ -701,6 +722,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
|
@ -1061,7 +1088,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"indexmap",
|
"indexmap 2.2.6",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1078,6 +1105,21 @@ dependencies = [
|
||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
@ -1094,7 +1136,21 @@ version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.7.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-polyfill",
|
||||||
|
"hash32",
|
||||||
|
"rustc_version",
|
||||||
|
"serde",
|
||||||
|
"spin 0.9.8",
|
||||||
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1326,6 +1382,17 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
|
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.6"
|
version = "2.2.6"
|
||||||
|
@ -1333,7 +1400,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1401,6 +1469,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"paste",
|
"paste",
|
||||||
|
"serde",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1972,6 +2041,18 @@ dependencies = [
|
||||||
"syn 2.0.66",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"embedded-io",
|
||||||
|
"heapless",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2441,9 +2522,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.203"
|
version = "1.0.204"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -2459,9 +2540,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.203"
|
version = "1.0.204"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2500,6 +2581,36 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "3.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.2.6",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with_macros",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "3.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.66",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serenity"
|
name = "serenity"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
|
@ -2566,6 +2677,9 @@ dependencies = [
|
||||||
"num",
|
"num",
|
||||||
"plotters",
|
"plotters",
|
||||||
"poise",
|
"poise",
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
"serde_with",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tesseract",
|
"tesseract",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2713,7 +2827,7 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap",
|
"indexmap 2.2.6",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2876,6 +2990,12 @@ dependencies = [
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -3212,7 +3332,7 @@ version = "0.22.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap 2.2.6",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
@ -3316,7 +3436,7 @@ checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"mini-moka",
|
"mini-moka",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
|
|
|
@ -7,10 +7,13 @@ edition = "2021"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
edit-distance = "2.1.0"
|
edit-distance = "2.1.0"
|
||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
kd-tree = "0.6.0"
|
kd-tree = { version="0.6.0", features=["serde"] }
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
plotters = { git="https://github.com/starlitcanopy/plotters.git", rev="986cd959362a2dbec8d1b25670fd083b904d7b8c" }
|
plotters = { git="https://github.com/starlitcanopy/plotters.git", rev="986cd959362a2dbec8d1b25670fd083b904d7b8c" }
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
|
postcard = { version="1.0.8", features=["use-std"] }
|
||||||
|
serde = "1.0.204"
|
||||||
|
serde_with = "3.8.3"
|
||||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono"] }
|
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono"] }
|
||||||
tesseract = "0.15.1"
|
tesseract = "0.15.1"
|
||||||
tokio = {version="1.38.0", features=["rt-multi-thread"]}
|
tokio = {version="1.38.0", features=["rt-multi-thread"]}
|
||||||
|
|
51
src/chart.rs
51
src/chart.rs
|
@ -1,11 +1,14 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{prelude::FromRow, SqlitePool};
|
use sqlx::{prelude::FromRow, SqlitePool};
|
||||||
|
|
||||||
use crate::context::Error;
|
use crate::context::Error;
|
||||||
|
|
||||||
// {{{ Difficuly
|
// {{{ Difficuly
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum Difficulty {
|
pub enum Difficulty {
|
||||||
PST,
|
PST,
|
||||||
PRS,
|
PRS,
|
||||||
|
@ -42,21 +45,58 @@ impl TryFrom<String> for Difficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
// {{{ Side
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Side {
|
||||||
|
Light,
|
||||||
|
Conflict,
|
||||||
|
Silent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Side {
|
||||||
|
pub const SIDES: [Self; 3] = [Self::Light, Self::Conflict, Self::Silent];
|
||||||
|
pub const SIDE_STRINGS: [&'static str; 3] = ["light", "conflict", "silent"];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn to_index(self) -> usize {
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Side {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
for (i, s) in Self::SIDE_STRINGS.iter().enumerate() {
|
||||||
|
if value == **s {
|
||||||
|
return Ok(Self::SIDES[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!("Cannot convert {} to difficulty", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
// {{{ Song
|
// {{{ Song
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
#[allow(dead_code)]
|
pub lowercase_title: String,
|
||||||
pub artist: String,
|
pub artist: String,
|
||||||
|
|
||||||
|
pub bpm: String,
|
||||||
|
pub pack: Option<String>,
|
||||||
|
pub side: Side,
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Chart
|
// {{{ Chart
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||||
pub struct Chart {
|
pub struct Chart {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub song_id: u32,
|
pub song_id: u32,
|
||||||
pub shorthand: Option<String>,
|
pub shorthand: Option<String>,
|
||||||
|
pub note_design: Option<String>,
|
||||||
|
|
||||||
pub difficulty: Difficulty,
|
pub difficulty: Difficulty,
|
||||||
pub level: String, // TODO: this could become an enum
|
pub level: String, // TODO: this could become an enum
|
||||||
|
@ -200,8 +240,12 @@ impl SongCache {
|
||||||
for song in songs {
|
for song in songs {
|
||||||
let song = Song {
|
let song = Song {
|
||||||
id: song.id as u32,
|
id: song.id as u32,
|
||||||
|
lowercase_title: song.title.to_lowercase(),
|
||||||
title: song.title,
|
title: song.title,
|
||||||
artist: song.artist,
|
artist: song.artist,
|
||||||
|
pack: song.pack,
|
||||||
|
bpm: song.bpm,
|
||||||
|
side: Side::try_from(song.side)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let song_id = song.id as usize;
|
let song_id = song.id as usize;
|
||||||
|
@ -225,6 +269,7 @@ impl SongCache {
|
||||||
chart_constant: chart.chart_constant as u32,
|
chart_constant: chart.chart_constant as u32,
|
||||||
note_count: chart.note_count as u32,
|
note_count: chart.note_count as u32,
|
||||||
cached_jacket: None,
|
cached_jacket: None,
|
||||||
|
note_design: chart.note_design,
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = chart.difficulty.to_index();
|
let index = chart.difficulty.to_index();
|
||||||
|
|
76
src/commands/chart.rs
Normal file
76
src/commands/chart.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use poise::serenity_prelude::{CreateAttachment, CreateEmbed, CreateMessage};
|
||||||
|
use sqlx::query;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
chart::Side,
|
||||||
|
context::{Context, Error},
|
||||||
|
score::guess_song_and_chart,
|
||||||
|
};
|
||||||
|
|
||||||
|
// {{{ Chart
|
||||||
|
/// Show a chart given it's name
|
||||||
|
#[poise::command(prefix_command, slash_command)]
|
||||||
|
pub async fn chart(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[rest]
|
||||||
|
#[description = "Name of chart to show (difficulty at the end)"]
|
||||||
|
name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
|
||||||
|
|
||||||
|
let attachement_name = "chart.png";
|
||||||
|
let icon_attachement = match chart.cached_jacket {
|
||||||
|
Some(bytes) => Some(CreateAttachment::bytes(bytes, attachement_name)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let play_count = query!(
|
||||||
|
"
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM plays
|
||||||
|
WHERE chart_id=?
|
||||||
|
",
|
||||||
|
chart.id
|
||||||
|
)
|
||||||
|
.fetch_one(&ctx.data().db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut embed = CreateEmbed::default()
|
||||||
|
.title(format!(
|
||||||
|
"{} [{:?} {}]",
|
||||||
|
&song.title, chart.difficulty, chart.level
|
||||||
|
))
|
||||||
|
.field("Note count", format!("{}", chart.note_count), true)
|
||||||
|
.field(
|
||||||
|
"Chart constant",
|
||||||
|
format!("{:.1}", chart.chart_constant as f32 / 100.0),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.field("Total plays", format!("{}", play_count.count), true)
|
||||||
|
.field("BPM", &song.bpm, true)
|
||||||
|
.field("Side", Side::SIDE_STRINGS[song.side.to_index()], true)
|
||||||
|
.field("Artist", &song.title, true);
|
||||||
|
|
||||||
|
if let Some(note_design) = &chart.note_design {
|
||||||
|
embed = embed.field("Note design", note_design, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pack) = &song.pack {
|
||||||
|
embed = embed.field("Pack", pack, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if icon_attachement.is_some() {
|
||||||
|
embed = embed.thumbnail(format!("attachment://{}", &attachement_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.channel_id()
|
||||||
|
.send_files(
|
||||||
|
ctx.http(),
|
||||||
|
icon_attachement,
|
||||||
|
CreateMessage::new().embed(embed),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// }}}
|
|
@ -1,2 +1,27 @@
|
||||||
|
use crate::context::{Context, Error};
|
||||||
|
|
||||||
|
pub mod chart;
|
||||||
pub mod score;
|
pub mod score;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
|
|
||||||
|
// {{{ Help
|
||||||
|
/// Show this help menu
|
||||||
|
#[poise::command(prefix_command, track_edits, slash_command)]
|
||||||
|
pub async fn help(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Specific command to show help about"]
|
||||||
|
#[autocomplete = "poise::builtins::autocomplete_command"]
|
||||||
|
command: Option<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
poise::builtins::help(
|
||||||
|
ctx,
|
||||||
|
command.as_deref(),
|
||||||
|
poise::builtins::HelpConfiguration {
|
||||||
|
extra_text_at_bottom: "For additional support, message @prescientmoon",
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
|
@ -10,27 +10,6 @@ use poise::serenity_prelude::{CreateAttachment, CreateEmbed, CreateMessage};
|
||||||
use poise::{serenity_prelude as serenity, CreateReply};
|
use poise::{serenity_prelude as serenity, CreateReply};
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
|
|
||||||
// {{{ Help
|
|
||||||
/// Show this help menu
|
|
||||||
#[poise::command(prefix_command, track_edits, slash_command)]
|
|
||||||
pub async fn help(
|
|
||||||
ctx: Context<'_>,
|
|
||||||
#[description = "Specific command to show help about"]
|
|
||||||
#[autocomplete = "poise::builtins::autocomplete_command"]
|
|
||||||
command: Option<String>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
poise::builtins::help(
|
|
||||||
ctx,
|
|
||||||
command.as_deref(),
|
|
||||||
poise::builtins::HelpConfiguration {
|
|
||||||
extra_text_at_bottom: "For additional support, message @prescientmoon",
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// }}}
|
|
||||||
// {{{ Score
|
// {{{ Score
|
||||||
/// Score management
|
/// Score management
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
|
|
|
@ -17,9 +17,8 @@ use poise::{
|
||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chart::Difficulty,
|
|
||||||
context::{Context, Error},
|
context::{Context, Error},
|
||||||
score::{guess_chart_name, DbPlay, Score},
|
score::{guess_song_and_chart, DbPlay, Score},
|
||||||
user::{discord_it_to_discord_user, User},
|
user::{discord_it_to_discord_user, User},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,22 +64,7 @@ pub async fn best(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = name.trim();
|
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
|
||||||
let (name, difficulty) = name
|
|
||||||
.strip_suffix("PST")
|
|
||||||
.zip(Some(Difficulty::PST))
|
|
||||||
.or_else(|| name.strip_suffix("[PST]").zip(Some(Difficulty::PST)))
|
|
||||||
.or_else(|| name.strip_suffix("PRS").zip(Some(Difficulty::PRS)))
|
|
||||||
.or_else(|| name.strip_suffix("[PRS]").zip(Some(Difficulty::PRS)))
|
|
||||||
.or_else(|| name.strip_suffix("FTR").zip(Some(Difficulty::FTR)))
|
|
||||||
.or_else(|| name.strip_suffix("[FTR]").zip(Some(Difficulty::FTR)))
|
|
||||||
.or_else(|| name.strip_suffix("ETR").zip(Some(Difficulty::ETR)))
|
|
||||||
.or_else(|| name.strip_suffix("[ETR]").zip(Some(Difficulty::ETR)))
|
|
||||||
.or_else(|| name.strip_suffix("BYD").zip(Some(Difficulty::BYD)))
|
|
||||||
.or_else(|| name.strip_suffix("[BYD]").zip(Some(Difficulty::BYD)))
|
|
||||||
.unwrap_or((&name, Difficulty::FTR));
|
|
||||||
|
|
||||||
let (song, chart) = guess_chart_name(name, &ctx.data().song_cache, Some(difficulty), true)?;
|
|
||||||
|
|
||||||
let play = query_as!(
|
let play = query_as!(
|
||||||
DbPlay,
|
DbPlay,
|
||||||
|
@ -137,22 +121,7 @@ pub async fn plot(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = name.trim();
|
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
|
||||||
let (name, difficulty) = name
|
|
||||||
.strip_suffix("PST")
|
|
||||||
.zip(Some(Difficulty::PST))
|
|
||||||
.or_else(|| name.strip_suffix("[PST]").zip(Some(Difficulty::PST)))
|
|
||||||
.or_else(|| name.strip_suffix("PRS").zip(Some(Difficulty::PRS)))
|
|
||||||
.or_else(|| name.strip_suffix("[PRS]").zip(Some(Difficulty::PRS)))
|
|
||||||
.or_else(|| name.strip_suffix("FTR").zip(Some(Difficulty::FTR)))
|
|
||||||
.or_else(|| name.strip_suffix("[FTR]").zip(Some(Difficulty::FTR)))
|
|
||||||
.or_else(|| name.strip_suffix("ETR").zip(Some(Difficulty::ETR)))
|
|
||||||
.or_else(|| name.strip_suffix("[ETR]").zip(Some(Difficulty::ETR)))
|
|
||||||
.or_else(|| name.strip_suffix("BYD").zip(Some(Difficulty::BYD)))
|
|
||||||
.or_else(|| name.strip_suffix("[BYD]").zip(Some(Difficulty::BYD)))
|
|
||||||
.unwrap_or((&name, Difficulty::FTR));
|
|
||||||
|
|
||||||
let (song, chart) = guess_chart_name(name, &ctx.data().song_cache, Some(difficulty), true)?;
|
|
||||||
|
|
||||||
let plays = query_as!(
|
let plays = query_as!(
|
||||||
DbPlay,
|
DbPlay,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
@ -19,7 +19,10 @@ pub struct UserContext {
|
||||||
|
|
||||||
impl UserContext {
|
impl UserContext {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn new(data_dir: PathBuf, db: SqlitePool) -> Result<Self, Error> {
|
pub async fn new(data_dir: PathBuf, cache_dir: PathBuf, db: SqlitePool) -> Result<Self, Error> {
|
||||||
|
fs::create_dir_all(&cache_dir)?;
|
||||||
|
fs::create_dir_all(&data_dir)?;
|
||||||
|
|
||||||
let mut song_cache = SongCache::new(&db).await?;
|
let mut song_cache = SongCache::new(&db).await?;
|
||||||
let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?;
|
let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::{collections::HashSet, fs, path::PathBuf, str::FromStr};
|
use std::{fs, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
use kd_tree::{KdMap, KdPoint};
|
use kd_tree::{KdMap, KdPoint};
|
||||||
use num::Integer;
|
use num::Integer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chart::{Difficulty, SongCache},
|
chart::{Difficulty, SongCache},
|
||||||
|
@ -14,8 +16,10 @@ use crate::{
|
||||||
pub const SPLIT_FACTOR: u32 = 8;
|
pub const SPLIT_FACTOR: u32 = 8;
|
||||||
pub const IMAGE_VEC_DIM: usize = (SPLIT_FACTOR * SPLIT_FACTOR * 3) as usize;
|
pub const IMAGE_VEC_DIM: usize = (SPLIT_FACTOR * SPLIT_FACTOR * 3) as usize;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[serde_as]
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ImageVec {
|
pub struct ImageVec {
|
||||||
|
#[serde_as(as = "[_; IMAGE_VEC_DIM]")]
|
||||||
pub colors: [f32; IMAGE_VEC_DIM],
|
pub colors: [f32; IMAGE_VEC_DIM],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,9 +77,9 @@ impl KdPoint for ImageVec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct JacketCache {
|
pub struct JacketCache {
|
||||||
// TODO: make this private
|
tree: KdMap<ImageVec, u32>,
|
||||||
pub tree: KdMap<ImageVec, u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JacketCache {
|
impl JacketCache {
|
||||||
|
@ -90,7 +94,7 @@ impl JacketCache {
|
||||||
|
|
||||||
fs::create_dir_all(&jacket_dir).expect("Could not create jacket dir");
|
fs::create_dir_all(&jacket_dir).expect("Could not create jacket dir");
|
||||||
|
|
||||||
let mut jackets: HashSet<(PathBuf, u32)> = HashSet::new();
|
let mut jackets = Vec::new();
|
||||||
let entries = fs::read_dir(data_dir.join("songs")).expect("Couldn't read songs directory");
|
let entries = fs::read_dir(data_dir.join("songs")).expect("Couldn't read songs directory");
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let dir = entry?;
|
let dir = entry?;
|
||||||
|
@ -120,7 +124,7 @@ impl JacketCache {
|
||||||
|
|
||||||
let (song, chart) = guess_chart_name(dir_name, &song_cache, difficulty, true)?;
|
let (song, chart) = guess_chart_name(dir_name, &song_cache, difficulty, true)?;
|
||||||
|
|
||||||
jackets.insert((file.path(), song.id));
|
jackets.push((file.path(), song.id));
|
||||||
|
|
||||||
let contents = fs::read(file.path())?.leak();
|
let contents = fs::read(file.path())?.leak();
|
||||||
|
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -30,6 +30,7 @@ async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) {
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let data_dir = var("SHIMMERING_DATA_DIR").expect("Missing `SHIMMERING_DATA_DIR` env var");
|
let data_dir = var("SHIMMERING_DATA_DIR").expect("Missing `SHIMMERING_DATA_DIR` env var");
|
||||||
|
let cache_dir = var("SHIMMERING_CACHE_DIR").expect("Missing `SHIMMERING_CACHE_DIR` env var");
|
||||||
|
|
||||||
let pool = SqlitePoolOptions::new()
|
let pool = SqlitePoolOptions::new()
|
||||||
.connect(&format!("sqlite://{}/db.sqlite", data_dir))
|
.connect(&format!("sqlite://{}/db.sqlite", data_dir))
|
||||||
|
@ -39,9 +40,10 @@ async fn main() {
|
||||||
// {{{ Poise options
|
// {{{ Poise options
|
||||||
let options = poise::FrameworkOptions {
|
let options = poise::FrameworkOptions {
|
||||||
commands: vec![
|
commands: vec![
|
||||||
commands::score::help(),
|
commands::help(),
|
||||||
commands::score::score(),
|
commands::score::score(),
|
||||||
commands::stats::stats(),
|
commands::stats::stats(),
|
||||||
|
commands::chart::chart(),
|
||||||
],
|
],
|
||||||
prefix_options: poise::PrefixFrameworkOptions {
|
prefix_options: poise::PrefixFrameworkOptions {
|
||||||
stripped_dynamic_prefix: Some(|_ctx, message, _user_ctx| {
|
stripped_dynamic_prefix: Some(|_ctx, message, _user_ctx| {
|
||||||
|
@ -76,11 +78,13 @@ async fn main() {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
println!("Logged in as {}", _ready.user.name);
|
println!("Logged in as {}", _ready.user.name);
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
let ctx = UserContext::new(PathBuf::from_str(&data_dir)?, pool).await?;
|
let ctx = UserContext::new(
|
||||||
|
PathBuf::from_str(&data_dir)?,
|
||||||
|
PathBuf::from_str(&cache_dir)?,
|
||||||
|
pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// for song in ctx.song_cache.lock().unwrap().songs() {
|
|
||||||
// song.lookup(Difficulty::BYD)
|
|
||||||
// }
|
|
||||||
Ok(ctx)
|
Ok(ctx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
35
src/score.rs
35
src/score.rs
|
@ -900,7 +900,38 @@ pub fn note_distribution_rects() -> (
|
||||||
// }}}
|
// }}}
|
||||||
// }}}
|
// }}}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Recognise chart name
|
// {{{ Recognise chart
|
||||||
|
fn strip_case_insensitive_suffix<'a>(string: &'a str, suffix: &str) -> Option<&'a str> {
|
||||||
|
let suffix = suffix.to_lowercase();
|
||||||
|
if string.to_lowercase().ends_with(&suffix) {
|
||||||
|
Some(&string[0..string.len() - suffix.len()])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guess_song_and_chart<'a>(
|
||||||
|
ctx: &'a UserContext,
|
||||||
|
name: &'a str,
|
||||||
|
) -> Result<(&'a Song, &'a Chart), Error> {
|
||||||
|
let name = name.trim();
|
||||||
|
let (name, difficulty) = name
|
||||||
|
.strip_suffix("PST")
|
||||||
|
.zip(Some(Difficulty::PST))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "[PST]").zip(Some(Difficulty::PST)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "PRS").zip(Some(Difficulty::PRS)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "[PRS]").zip(Some(Difficulty::PRS)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "FTR").zip(Some(Difficulty::FTR)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "[FTR]").zip(Some(Difficulty::FTR)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "ETR").zip(Some(Difficulty::ETR)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "[ETR]").zip(Some(Difficulty::ETR)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "BYD").zip(Some(Difficulty::BYD)))
|
||||||
|
.or_else(|| strip_case_insensitive_suffix(name, "[BYD]").zip(Some(Difficulty::BYD)))
|
||||||
|
.unwrap_or((&name, Difficulty::FTR));
|
||||||
|
|
||||||
|
guess_chart_name(name, &ctx.song_cache, Some(difficulty), true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs a specialized fuzzy-search through all charts in the game.
|
/// Runs a specialized fuzzy-search through all charts in the game.
|
||||||
///
|
///
|
||||||
/// The `unsafe_heuristics` toggle increases the amount of resolvable queries, but might let in
|
/// The `unsafe_heuristics` toggle increases the amount of resolvable queries, but might let in
|
||||||
|
@ -928,7 +959,7 @@ pub fn guess_chart_name<'a>(
|
||||||
item.charts().next()?
|
item.charts().next()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let song_title = song.title.to_lowercase();
|
let song_title = &song.lowercase_title;
|
||||||
distance_vec.clear();
|
distance_vec.clear();
|
||||||
|
|
||||||
let base_distance = edit_distance(&text, &song_title);
|
let base_distance = edit_distance(&text, &song_title);
|
||||||
|
|
Loading…
Reference in a new issue