diff --git a/sdgb-api/src/helper/mod.rs b/sdgb-api/src/helper/mod.rs index faead71..1894cd1 100644 --- a/sdgb-api/src/helper/mod.rs +++ b/sdgb-api/src/helper/mod.rs @@ -11,4 +11,4 @@ pub fn level_name(level: u32) -> &'static str { } mod music_db; -pub use music_db::{Level, MUSIC_DB, MusicInfo}; +pub use music_db::{Level, MusicInfo, query_music, query_music_level, preload_db}; diff --git a/sdgb-api/src/helper/music_db/mod.rs b/sdgb-api/src/helper/music_db/mod.rs index b009c63..3edcf51 100644 --- a/sdgb-api/src/helper/music_db/mod.rs +++ b/sdgb-api/src/helper/music_db/mod.rs @@ -25,6 +25,22 @@ pub struct Level { type MusicDB = FxHashMap; +pub fn preload_db() { + _ = &*MUSIC_DB; +} + +pub fn query_music(music_id: &u32) -> Option<&'static MusicInfo> { + MUSIC_DB.as_ref()?.get(music_id) +} +pub fn query_music_level(music_id: u32, level: u32) -> Option<&'static Level> { + MUSIC_DB + .as_ref()? + .get(&music_id)? + .levels + .iter() + .find(|d| d.level == level) +} + pub static MUSIC_DB: LazyLock> = LazyLock::new(|| { let time = SystemTime::now(); info!("loading musicDB..."); diff --git a/sdgb-api/src/title/model/get_user_music_api/mod.rs b/sdgb-api/src/title/model/get_user_music_api/mod.rs index e9b7f2d..27398d1 100644 --- a/sdgb-api/src/title/model/get_user_music_api/mod.rs +++ b/sdgb-api/src/title/model/get_user_music_api/mod.rs @@ -1,6 +1,12 @@ +use std::fmt::Display; + use serde::Deserialize; use serde::Serialize; +use crate::helper::level_name; +use crate::helper::query_music; +use crate::helper::query_music_level; + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetUserMusicApi { @@ -31,11 +37,78 @@ pub struct UserMusicDetail { pub music_id: u32, pub level: u32, pub play_count: i64, + /// 达成率 pub achievement: i64, + + /// Full Combo + /// + /// - 0: None + /// - 1: Full Combo + /// - 2: Full Combo+ + /// - 3: All Perfect + /// - 4: All Perfect+ pub combo_status: i64, + + /// Full Sync + /// + /// - 0: None + /// - 1: FullSync + /// - 2: FullSync+ + /// - 3: FullSync DX + /// - 4: Full Sync DX+ + /// - 5: SYNC pub sync_status: i64, + pub deluxscore_max: i64, pub score_rank: i64, + pub ext_num1: i64, pub ext_num2: i64, } + +impl Display for UserMusicDetail { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(music_title) = query_music(&self.music_id).map(|i| &i.name) { + f.write_fmt(format_args!("曲目: \t{music_title}\n"))?; + } + f.write_fmt(format_args!("难度: \t{}\n", level_name(self.level)))?; + f.write_fmt(format_args!( + "达成率: \t{}.{:04}%\n", + self.achievement / 10000, + self.achievement % 10000 + ))?; + + f.write_fmt(format_args!( + "达成状态: \t{}\n", + match self.combo_status { + 0 => "无", + 1 => "Full Combo", + 2 => "Full Combo+", + 3 => "All Perfect", + 4 => "All Perfect+", + _ => "未知", + } + ))?; + + f.write_fmt(format_args!( + "同步状态: \t{}\n", + match self.sync_status { + 0 => "无", + 1 => "Full Sync", + 2 => "Full Sync+", + 3 => "Full Sync DX", + 4 => "Full Sync DX+", + _ => "未知", + } + ))?; + + f.write_fmt(format_args!("DX 分数: \t{}\n", self.deluxscore_max))?; + + if let Some(level) = query_music_level(self.music_id, self.level) { + let rating = level.dx_rating(self.achievement as _); + f.write_fmt(format_args!("DX RATING: \t{rating}"))?; + } + + Ok(()) + } +} diff --git a/sdgb-api/src/title/model/get_user_rating_api/dxrating/conversion/mod.rs b/sdgb-api/src/title/model/get_user_rating_api/dxrating/conversion/mod.rs index 9e6b225..775cc10 100644 --- a/sdgb-api/src/title/model/get_user_rating_api/dxrating/conversion/mod.rs +++ b/sdgb-api/src/title/model/get_user_rating_api/dxrating/conversion/mod.rs @@ -1,5 +1,5 @@ use crate::{ - helper::MUSIC_DB, + helper::query_music, title::model::get_user_rating_api::{ MusicRating, UserRating, dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId}, @@ -37,9 +37,7 @@ impl TryFrom<&MusicRating> for DxMusicRecord { type Error = ConversionError; fn try_from(value: &MusicRating) -> Result { - let music_title = MUSIC_DB - .as_ref() - .and_then(|db| db.get(&value.music_id)) + let music_title = query_music(&value.music_id) .map(|info| info.name.clone()) .ok_or(ConversionError::MusicNotInDB)?; diff --git a/sdgb-api/src/title/model/get_user_rating_api/mod.rs b/sdgb-api/src/title/model/get_user_rating_api/mod.rs index fdf4744..b2e34d1 100644 --- a/sdgb-api/src/title/model/get_user_rating_api/mod.rs +++ b/sdgb-api/src/title/model/get_user_rating_api/mod.rs @@ -3,8 +3,9 @@ use std::fmt::Display; use serde::Deserialize; use serde::Serialize; -use crate::helper::MUSIC_DB; use crate::helper::level_name; +use crate::helper::query_music; +use crate::helper::query_music_level; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -150,22 +151,11 @@ impl Display for MusicRating { impl MusicRating { pub fn music_title(&self) -> Option { - MUSIC_DB - .as_ref()? - .get(&self.music_id) - .map(|music_info| music_info.name.clone()) + Some(query_music(&self.music_id).as_ref()?.name.clone()) } pub fn dx_rating(&self) -> Option { - Some( - MUSIC_DB - .as_ref()? - .get(&self.music_id)? - .levels - .iter() - .find(|d| d.level == self.level)? - .dx_rating(self.achievement), - ) + Some(query_music_level(self.music_id, self.level)?.dx_rating(self.achievement)) } } diff --git a/sdgb-cli/src/main.rs b/sdgb-cli/src/main.rs index 1d13a34..bbebaea 100644 --- a/sdgb-cli/src/main.rs +++ b/sdgb-cli/src/main.rs @@ -13,7 +13,7 @@ use spdlog::{Level, LevelFilter::MoreSevereEqual, sink::StdStreamSink, terminal_ use sdgb_api::{ all_net::QRCode, auth_lite::{SDGB, SDHJ, delivery_raw}, - helper::MUSIC_DB, + helper::preload_db, title::{ MaiVersionExt, Sdgb1_50, methods::APIMethod, @@ -69,8 +69,6 @@ async fn main() -> Result<(), Box> { } })?; - let _ = &*MUSIC_DB; - let Cli { command, machine_readable, @@ -78,6 +76,7 @@ async fn main() -> Result<(), Box> { let human_readable = !machine_readable; let client = ClientBuilder::default().build_async().await?; + preload_db(); // TODO: refactor via enum_dispatch match command { @@ -112,8 +111,14 @@ async fn main() -> Result<(), Box> { index = Some(next_index); } - // TODO: `Display` support for MusicDetail - json_display(music_detail)?; + if human_readable { + for detail in music_detail { + println!("{detail}"); + println!("----------"); + } + } else { + json_display(music_detail)?; + } } commands::Commands::Rating { user_id, format } => { let rating: GetUserRatingApiResp = Sdgb1_50::request(