feat: display music details

This commit is contained in:
mokurin000
2025-08-02 00:52:09 +08:00
parent 23d8345b0e
commit 2cb3c77d92
6 changed files with 106 additions and 24 deletions

View File

@@ -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};

View File

@@ -25,6 +25,22 @@ pub struct Level {
type MusicDB = FxHashMap<u32, MusicInfo>;
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<Option<MusicDB>> = LazyLock::new(|| {
let time = SystemTime::now();
info!("loading musicDB...");

View File

@@ -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(())
}
}

View File

@@ -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<Self, Self::Error> {
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)?;

View File

@@ -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<String> {
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<u32> {
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))
}
}

View File

@@ -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<dyn snafu::Error>> {
}
})?;
let _ = &*MUSIC_DB;
let Cli {
command,
machine_readable,
@@ -78,6 +76,7 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
let human_readable = !machine_readable;
let client = ClientBuilder::default().build_async().await?;
preload_db();
// TODO: refactor via enum_dispatch
match command {
@@ -112,9 +111,15 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
index = Some(next_index);
}
// TODO: `Display` support for MusicDetail
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(
&client,