feat: convert music detail to dxrating

This commit is contained in:
mokurin000
2025-08-02 10:03:02 +08:00
parent d337c48ff1
commit 9e17df0624
10 changed files with 96 additions and 36 deletions

View File

@@ -58,7 +58,7 @@ impl QRCode<'_> {
2 => Err(QRLoginError::QRCodeExpired10), 2 => Err(QRLoginError::QRCodeExpired10),
1 => Err(QRLoginError::QRCodeExpired30), 1 => Err(QRLoginError::QRCodeExpired30),
50 => Err(QRLoginError::BadSingature), 50 => Err(QRLoginError::BadSingature),
error_kind @ _ => Err(QRLoginError::Unknown { error_kind }), error_kind => Err(QRLoginError::Unknown { error_kind }),
} }
} }
} }

View File

@@ -29,8 +29,8 @@ pub fn preload_db() {
_ = &*MUSIC_DB; _ = &*MUSIC_DB;
} }
pub fn query_music(music_id: &u32) -> Option<&'static MusicInfo> { pub fn query_music(music_id: u32) -> Option<&'static MusicInfo> {
MUSIC_DB.as_ref()?.get(music_id) MUSIC_DB.as_ref()?.get(&music_id)
} }
pub fn query_music_level(music_id: u32, level: u32) -> Option<&'static Level> { pub fn query_music_level(music_id: u32, level: u32) -> Option<&'static Level> {
MUSIC_DB MUSIC_DB

View File

@@ -1,8 +1,9 @@
use crate::{ use crate::{
helper::query_music, helper::query_music,
title::model::get_user_rating_api::{ title::model::{
MusicRating, UserRating,
dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId}, dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId},
get_user_music_api::UserMusicDetail,
get_user_rating_api::{MusicRating, UserRating},
}, },
}; };
@@ -33,21 +34,54 @@ impl TryFrom<u32> for DxLevelName {
} }
} }
impl TryFrom<&MusicRating> for DxMusicRecord { impl TryFrom<&UserMusicDetail> for DxMusicRecord {
type Error = ConversionError; type Error = ConversionError;
fn try_from(value: &MusicRating) -> Result<Self, Self::Error> { fn try_from(
let music_title = query_music(&value.music_id) &UserMusicDetail {
music_id,
level,
achievement,
..
}: &UserMusicDetail,
) -> Result<Self, Self::Error> {
let music_title = query_music(music_id)
.map(|info| info.name.clone()) .map(|info| info.name.clone())
.ok_or(ConversionError::MusicNotInDB)?; .ok_or(ConversionError::MusicNotInDB)?;
Ok(Self { Ok(Self {
sheet_id: DxSheetId { sheet_id: DxSheetId {
music_title, music_title,
level: DxLevelName::try_from(value.level)?, level: DxLevelName::try_from(level)?,
dx_version: value.music_id >= 10000, dx_version: music_id >= 10000,
}, },
achievement_rate: (value.achievement as f64) / 10000.0, achievement_rate: (achievement as f64) / 10000.0,
})
}
}
impl TryFrom<&MusicRating> for DxMusicRecord {
type Error = ConversionError;
fn try_from(
&MusicRating {
music_id,
level,
achievement,
..
}: &MusicRating,
) -> Result<Self, Self::Error> {
let music_title = query_music(music_id)
.map(|info| info.name.clone())
.ok_or(ConversionError::MusicNotInDB)?;
Ok(Self {
sheet_id: DxSheetId {
music_title,
level: DxLevelName::try_from(level)?,
dx_version: music_id >= 10000,
},
achievement_rate: (achievement as f64) / 10000.0,
}) })
} }
} }

View File

@@ -70,7 +70,7 @@ pub struct UserMusicDetail {
impl Display for UserMusicDetail { impl Display for UserMusicDetail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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) { 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{music_title}\n"))?;
} }
f.write_fmt(format_args!("难度名称: \t{}\n", level_name(self.level)))?; f.write_fmt(format_args!("难度名称: \t{}\n", level_name(self.level)))?;

View File

@@ -151,12 +151,10 @@ impl Display for MusicRating {
impl MusicRating { impl MusicRating {
pub fn music_title(&self) -> Option<String> { pub fn music_title(&self) -> Option<String> {
Some(query_music(&self.music_id).as_ref()?.name.clone()) Some(query_music(self.music_id).as_ref()?.name.clone())
} }
pub fn dx_rating(&self) -> Option<u32> { pub fn dx_rating(&self) -> Option<u32> {
Some(query_music_level(self.music_id, self.level)?.dx_rating(self.achievement)) Some(query_music_level(self.music_id, self.level)?.dx_rating(self.achievement))
} }
} }
pub mod dxrating;

View File

@@ -14,14 +14,6 @@ mod get_user_data_api;
pub use get_user_data_api::{GetUserDataApi, GetUserDataApiResp, UserData}; pub use get_user_data_api::{GetUserDataApi, GetUserDataApiResp, UserData};
mod get_user_rating_api; mod get_user_rating_api;
pub use get_user_rating_api::dxrating::{
DataVersion,
DxCalculatedEntries, // entries
DxLevelName, // level name
DxMusicRecord,
DxRatingNet,
DxSheetId,
};
pub use get_user_rating_api::{ pub use get_user_rating_api::{
GetUserRatingApi, GetUserRatingApi,
GetUserRatingApiResp, // api GetUserRatingApiResp, // api
@@ -32,3 +24,14 @@ pub use get_user_rating_api::{
mod get_user_music_api; mod get_user_music_api;
pub use get_user_music_api::{GetUserMusicApi, GetUserMusicApiResp, UserMusic}; pub use get_user_music_api::{GetUserMusicApi, GetUserMusicApiResp, UserMusic};
mod dxrating;
pub use dxrating::{
DataVersion,
DxCalculatedEntries, // entries
DxLevelName, // level name
DxMusicRecord,
DxRatingNet,
DxSheetId,
};

View File

@@ -62,7 +62,7 @@ impl UserLoginApiResp {
100 => Some(LoginError::AlreadyLogged), 100 => Some(LoginError::AlreadyLogged),
102 => Some(LoginError::QRCodeExpired), 102 => Some(LoginError::QRCodeExpired),
103 => Some(LoginError::AccountUnregistered), 103 => Some(LoginError::AccountUnregistered),
error @ _ => Some(LoginError::Unknown { error }), error => Some(LoginError::Unknown { error }),
} }
} }
} }

View File

@@ -58,6 +58,13 @@ pub enum Commands {
MusicDetail { MusicDetail {
#[arg(short, long)] #[arg(short, long)]
user_id: u32, user_id: u32,
/// JSON format.
///
/// - `origin`: official json response
/// - `dx_rating_net`: DxRatingNet Format
#[arg(short, long, default_value_t = RatingFormat::default())]
format: RatingFormat,
}, },
/// Retrieve full userdata /// Retrieve full userdata
@@ -89,7 +96,7 @@ pub enum Commands {
}, },
} }
#[derive(Default, EnumString)] #[derive(Debug, Default, EnumString)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum RatingFormat { pub enum RatingFormat {
#[default] #[default]

View File

@@ -18,10 +18,10 @@ use sdgb_api::{
MaiVersionExt, Sdgb1_50, MaiVersionExt, Sdgb1_50,
methods::APIMethod, methods::APIMethod,
model::{ model::{
DataVersion, DxCalculatedEntries, DxRatingNet, GetUserDataApi, GetUserDataApiResp, DataVersion, DxCalculatedEntries, DxMusicRecord, DxRatingNet, GetUserDataApi,
GetUserMusicApi, GetUserMusicApiResp, GetUserPreviewApi, GetUserPreviewApiResp, GetUserDataApiResp, GetUserMusicApi, GetUserMusicApiResp, GetUserPreviewApi,
GetUserRatingApi, GetUserRatingApiResp, Ping, PingResp, UserLogoutApi, GetUserPreviewApiResp, GetUserRatingApi, GetUserRatingApiResp, Ping, PingResp,
UserLogoutApiResp, UserLogoutApi, UserLogoutApiResp,
}, },
}, },
}; };
@@ -80,7 +80,7 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
// TODO: refactor via enum_dispatch // TODO: refactor via enum_dispatch
match command { match command {
Commands::MusicDetail { user_id } => { Commands::MusicDetail { user_id, format } => {
let mut music_detail = Vec::new(); let mut music_detail = Vec::new();
let mut index = None; let mut index = None;
@@ -111,13 +111,31 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
index = Some(next_index); index = Some(next_index);
} }
if human_readable { match (human_readable, format) {
(true, _) => {
for detail in music_detail { for detail in music_detail {
println!("{detail}"); println!("{detail}");
println!("----------"); println!("----------");
} }
} else { }
json_display(music_detail)?; (false, RatingFormat::Origin) => json_display(music_detail)?,
(false, RatingFormat::DxRatingNet) => {
let dx_export = Vec::from_iter(
music_detail
.iter()
.map(|music| {
DxMusicRecord::try_from(music).inspect_err(|e| {
warn!("failed to process {}: {e}", music.music_id)
})
})
.flatten(),
);
json_display(dx_export)?;
}
(_, format) => {
error!("{format:?} was not supported yet");
json_display(())?;
}
} }
} }
Commands::Rating { user_id, format } => { Commands::Rating { user_id, format } => {