feat: initial support for GetUserRating
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1478,6 +1478,7 @@ dependencies = [
|
|||||||
"palc",
|
"palc",
|
||||||
"redb",
|
"redb",
|
||||||
"sdgb-api",
|
"sdgb-api",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snafu",
|
"snafu",
|
||||||
"spdlog-rs",
|
"spdlog-rs",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ spdlog-rs = { version = "0.4.3", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] }
|
snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] }
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.141"
|
||||||
strum = { version = "0.27.2", features = ["derive"] }
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
|
|||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# sdgb-utils-rs
|
||||||
|
|
||||||
|
- SBGA 舞萌DX API 文档参考
|
||||||
|
- “裸” cli 工具,没多少人性化功能
|
||||||
|
- 暂时不完整开放,留在私仓
|
||||||
@@ -19,6 +19,9 @@ tokio = { workspace = true, optional = true }
|
|||||||
compio = { workspace = true, optional = true }
|
compio = { workspace = true, optional = true }
|
||||||
spdlog-rs = { workspace = true }
|
spdlog-rs = { workspace = true }
|
||||||
|
|
||||||
|
# (de)serialization
|
||||||
|
serde = { workspace = true }
|
||||||
|
|
||||||
# hashing
|
# hashing
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
hmac-sha256 = { version = "1.1.12", features = ["digest010", "traits010"] }
|
hmac-sha256 = { version = "1.1.12", features = ["digest010", "traits010"] }
|
||||||
@@ -30,8 +33,6 @@ chrono = "0.4.41"
|
|||||||
# network request
|
# network request
|
||||||
nyquest = { version = "0.2.0", features = ["async", "json"] }
|
nyquest = { version = "0.2.0", features = ["async", "json"] }
|
||||||
|
|
||||||
# (de)serialization
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
|
||||||
|
|
||||||
# compression / encryption
|
# compression / encryption
|
||||||
flate2 = "1.1.2"
|
flate2 = "1.1.2"
|
||||||
|
|||||||
15
sdgb-api/src/helper/mod.rs
Normal file
15
sdgb-api/src/helper/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
pub fn level_name(level: u32) -> &'static str {
|
||||||
|
match level {
|
||||||
|
0 => "BASIC",
|
||||||
|
1 => "ADVANCED",
|
||||||
|
2 => "EXPERT",
|
||||||
|
3 => "MASTER",
|
||||||
|
4 => "RE: MASTER",
|
||||||
|
5 => "UTAGE",
|
||||||
|
_ => "Unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: MusicDB lazy load
|
||||||
|
// struct MusicDB;
|
||||||
|
// static MUSIC_DB: LazyLock<MusicDB> = LazyLock::new(|| unimplemented!());
|
||||||
@@ -2,6 +2,8 @@ pub mod all_net;
|
|||||||
pub mod auth_lite;
|
pub mod auth_lite;
|
||||||
pub mod title;
|
pub mod title;
|
||||||
|
|
||||||
|
pub mod helper;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub use error::ApiError;
|
pub use error::ApiError;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::helper::level_name;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GetUserRatingApi {
|
pub struct GetUserRatingApi {
|
||||||
@@ -17,21 +21,22 @@ pub struct GetUserRatingApiResp {
|
|||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UserRating {
|
pub struct UserRating {
|
||||||
|
/// total rating, now it's 0
|
||||||
pub rating: i64,
|
pub rating: i64,
|
||||||
/// b35
|
/// b35
|
||||||
pub rating_list: Vec<RatingList>,
|
pub rating_list: Vec<MusicRating>,
|
||||||
/// b15
|
/// b15
|
||||||
pub new_rating_list: Vec<RatingList>,
|
pub new_rating_list: Vec<MusicRating>,
|
||||||
/// 候补 b35
|
/// 候补 b35
|
||||||
pub next_rating_list: Vec<RatingList>,
|
pub next_rating_list: Vec<MusicRating>,
|
||||||
/// 候补 b15
|
/// 候补 b15
|
||||||
pub next_new_rating_list: Vec<RatingList>,
|
pub next_new_rating_list: Vec<MusicRating>,
|
||||||
pub udemae: Udemae,
|
pub udemae: Udemae,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RatingList {
|
pub struct MusicRating {
|
||||||
/// Maimai music id
|
/// Maimai music id
|
||||||
pub music_id: u32,
|
pub music_id: u32,
|
||||||
/// difficulty
|
/// difficulty
|
||||||
@@ -83,3 +88,43 @@ pub struct Udemae {
|
|||||||
#[serde(rename = "NpcLoseNum")]
|
#[serde(rename = "NpcLoseNum")]
|
||||||
pub npc_lose_num2: i64,
|
pub npc_lose_num2: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for GetUserRatingApiResp {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("用户ID: {}\n", self.user_id))?;
|
||||||
|
|
||||||
|
f.write_str("\n---------- B35 ----------\n")?;
|
||||||
|
for b35 in &self.user_rating.rating_list {
|
||||||
|
f.write_fmt(format_args!("{b35}\n---\n"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str("\n---------- B15 ----------\n")?;
|
||||||
|
for b15 in &self.user_rating.new_rating_list {
|
||||||
|
f.write_fmt(format_args!("{b15}\n---\n"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MusicRating {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("歌曲ID: \t{}\n", self.music_id))?;
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"谱面版本: \t{}\n",
|
||||||
|
match (self.music_id / 10000) % 10 {
|
||||||
|
0 => "SD",
|
||||||
|
1 => "DX",
|
||||||
|
_ => "宴",
|
||||||
|
}
|
||||||
|
))?;
|
||||||
|
|
||||||
|
f.write_fmt(format_args!("游玩难度: \t{}\n", level_name(self.level)))?;
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"达成率: \t{}.{}%",
|
||||||
|
self.achievement / 10000,
|
||||||
|
self.achievement % 10000
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ pub struct UserLoginApi {
|
|||||||
pub acsess_code: String,
|
pub acsess_code: String,
|
||||||
pub place_id: String,
|
pub place_id: String,
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
/// set to `false` is fine
|
/// false 的情况,二维码扫描后半小时可登录。
|
||||||
|
///
|
||||||
|
/// true 的情况,可延长至二维码扫描后的两小时可登录。
|
||||||
pub is_continue: bool,
|
pub is_continue: bool,
|
||||||
/// fixed to 0
|
/// fixed to 0
|
||||||
pub generic_flag: u8,
|
pub generic_flag: u8,
|
||||||
|
|||||||
@@ -16,11 +16,20 @@ fetchall = ["dep:redb", "dep:futures-util"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
sdgb-api = { workspace = true, features = ["bincode"] }
|
sdgb-api = { workspace = true, features = ["bincode"] }
|
||||||
|
|
||||||
spdlog-rs = { workspace = true }
|
# (de)serialization
|
||||||
snafu = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
|
|
||||||
|
# logging / errors
|
||||||
|
spdlog-rs = { workspace = true }
|
||||||
|
snafu = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
|
# kv database
|
||||||
redb = { workspace = true, optional = true }
|
redb = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
# async runtime
|
||||||
tokio = { workspace = true, features = ["macros"], optional = true }
|
tokio = { workspace = true, features = ["macros"], optional = true }
|
||||||
compio = { workspace = true, features = ["macros"], optional = true }
|
compio = { workspace = true, features = ["macros"], optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ use strum::EnumString;
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(about = "SDGB api tool", long_about = env!("CARGO_PKG_DESCRIPTION"))]
|
#[command(about = "SDGB api tool", long_about = env!("CARGO_PKG_DESCRIPTION"))]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
|
/// try to generate human readable output.
|
||||||
|
#[arg(short = 'M', long)]
|
||||||
|
pub machine_readable: bool,
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
@@ -37,6 +40,10 @@ pub enum Commands {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
user_id: u32,
|
user_id: u32,
|
||||||
},
|
},
|
||||||
|
Rating {
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_id: u32,
|
||||||
|
},
|
||||||
|
|
||||||
// below requires login
|
// below requires login
|
||||||
Userdata {
|
Userdata {
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ use sdgb_api::{
|
|||||||
MaiVersionExt, Sdgb1_40, Sdgb1_50,
|
MaiVersionExt, Sdgb1_40, Sdgb1_50,
|
||||||
methods::APIMethod,
|
methods::APIMethod,
|
||||||
model::{
|
model::{
|
||||||
GetUserDataApi, GetUserDataApiResp, GetUserPreviewApi, GetUserPreviewApiResp, Ping,
|
GetUserDataApi, GetUserDataApiResp, GetUserPreviewApi, GetUserPreviewApiResp,
|
||||||
PingResp, UserLogoutApi, UserLogoutApiResp,
|
GetUserRatingApi, GetUserRatingApiResp, Ping, PingResp, UserLogoutApi,
|
||||||
|
UserLogoutApiResp,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use spdlog::{error, info, warn};
|
use spdlog::{error, info, warn};
|
||||||
|
|
||||||
use crate::{commands::Cli, utils::login_action};
|
use crate::{
|
||||||
|
commands::Cli,
|
||||||
|
utils::{human_readable_display, login_action},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "fetchall")]
|
#[cfg(feature = "fetchall")]
|
||||||
mod cache;
|
mod cache;
|
||||||
@@ -48,12 +52,27 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let Cli { command } = <Cli as Parser>::parse();
|
let Cli {
|
||||||
|
command,
|
||||||
|
machine_readable,
|
||||||
|
} = <Cli as Parser>::parse();
|
||||||
|
let human_readable = !machine_readable;
|
||||||
|
|
||||||
let client = ClientBuilder::default().build_async().await?;
|
let client = ClientBuilder::default().build_async().await?;
|
||||||
|
|
||||||
// TODO: refactor via enum_dispatch
|
// TODO: refactor via enum_dispatch
|
||||||
match command {
|
match command {
|
||||||
|
commands::Commands::Rating { user_id } => {
|
||||||
|
let rating: GetUserRatingApiResp = Sdgb1_50::request(
|
||||||
|
&client,
|
||||||
|
APIMethod::GetUserRatingApi,
|
||||||
|
user_id,
|
||||||
|
GetUserRatingApi { user_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
human_readable_display(rating, human_readable)?;
|
||||||
|
}
|
||||||
commands::Commands::Logout { user_id, timestamp } => {
|
commands::Commands::Logout { user_id, timestamp } => {
|
||||||
let logout: UserLogoutApiResp = Sdgb1_50::request(
|
let logout: UserLogoutApiResp = Sdgb1_50::request(
|
||||||
&client,
|
&client,
|
||||||
@@ -77,7 +96,7 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("{preview}");
|
human_readable_display(preview, human_readable)?;
|
||||||
}
|
}
|
||||||
commands::Commands::Ping => {
|
commands::Commands::Ping => {
|
||||||
let decoded: PingResp = Sdgb1_40::request(
|
let decoded: PingResp = Sdgb1_40::request(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::{fmt::Display, io::stdout};
|
||||||
|
|
||||||
use nyquest_preset::nyquest::AsyncClient;
|
use nyquest_preset::nyquest::AsyncClient;
|
||||||
use sdgb_api::{
|
use sdgb_api::{
|
||||||
ApiError,
|
ApiError,
|
||||||
@@ -7,6 +9,7 @@ use sdgb_api::{
|
|||||||
model::{UserLoginApi, UserLoginApiResp, UserLogoutApi, UserLogoutApiResp},
|
model::{UserLoginApi, UserLoginApiResp, UserLogoutApi, UserLogoutApiResp},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Serialize;
|
||||||
use spdlog::info;
|
use spdlog::info;
|
||||||
|
|
||||||
pub async fn login_action<R>(
|
pub async fn login_action<R>(
|
||||||
@@ -44,3 +47,17 @@ pub async fn login_action<R>(
|
|||||||
info!("logout: {logout_resp:?}");
|
info!("logout: {logout_resp:?}");
|
||||||
Ok(return_data)
|
Ok(return_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn human_readable_display(
|
||||||
|
value: impl Display + Serialize,
|
||||||
|
human_readable: bool,
|
||||||
|
) -> Result<(), Box<dyn snafu::Error>> {
|
||||||
|
if human_readable {
|
||||||
|
println!("{value}");
|
||||||
|
} else {
|
||||||
|
let lock = stdout().lock();
|
||||||
|
serde_json::to_writer_pretty(lock, &value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user