feat: initial support of method call
This commit is contained in:
@@ -11,12 +11,18 @@ snafu = { workspace = true }
|
||||
digest = "0.10.7"
|
||||
hmac-sha256 = { version = "1.1.12", features = ["digest010", "traits010"] }
|
||||
|
||||
# other utils
|
||||
chrono = "0.4.41"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
|
||||
# network request
|
||||
nyquest = { version = "0.2.0", features = ["async", "json"] }
|
||||
|
||||
# (de)serialization
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.141"
|
||||
|
||||
flate2 = "1.1.2"
|
||||
cbc = "0.1.2"
|
||||
aes = "0.8.4"
|
||||
md5 = "0.8.0"
|
||||
|
||||
@@ -7,9 +7,14 @@ pub enum ApiError {
|
||||
PadError { error: PadError },
|
||||
#[snafu(display("unpad data: {error}"))]
|
||||
UnpadError { error: UnpadError },
|
||||
|
||||
#[snafu(display("io error: {source}"))]
|
||||
#[snafu(context(false))]
|
||||
IOError { source: std::io::Error },
|
||||
|
||||
#[snafu(display("json error: {source}"))]
|
||||
#[snafu(context(false))]
|
||||
JSONError { source: serde_json::Error },
|
||||
}
|
||||
|
||||
impl From<UnpadError> for ApiError {
|
||||
|
||||
63
sdgb-api/src/title/methods/mod.rs
Normal file
63
sdgb-api/src/title/methods/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
#[derive(strum::IntoStaticStr)]
|
||||
pub enum APIMethod {
|
||||
GetGameChargeApi,
|
||||
GetGameEventApi,
|
||||
GetGameNgMusicIdApi,
|
||||
GetGameNgWordListApi,
|
||||
GetGameRankingApi,
|
||||
GetGameSettingApi,
|
||||
GetGameTournamentInfoApi,
|
||||
GetTransferFriendApi,
|
||||
GetUserActivityApi,
|
||||
GetUserCardApi,
|
||||
GetUserCharacterApi,
|
||||
GetUserChargeApi,
|
||||
GetUserCourseApi,
|
||||
GetUserDataApi,
|
||||
GetUserExtendApi,
|
||||
GetUserFavoriteApi,
|
||||
GetUserFavoriteItemApi,
|
||||
GetUserFriendSeasonRankingApi,
|
||||
GetUserGhostApi,
|
||||
GetUserItemApi,
|
||||
GetUserLoginBonusApi,
|
||||
GetUserMapApi,
|
||||
GetUserMusicApi,
|
||||
GetUserOptionApi,
|
||||
GetUserPortraitApi,
|
||||
GetUserPreviewApi,
|
||||
GetUserRatingApi,
|
||||
GetUserRecommendRateMusicApi,
|
||||
GetUserRecommendSelectMusicApi,
|
||||
GetUserRegionApi,
|
||||
GetUserScoreRankingApi,
|
||||
Ping,
|
||||
UploadUserPhotoApi,
|
||||
UploadUserPlaylogApi,
|
||||
UploadUserPortraitApi,
|
||||
UpsertClientBookkeepingApi,
|
||||
UpsertClientSettingApi,
|
||||
UpsertClientTestmodeApi,
|
||||
UpsertClientUploadApi,
|
||||
UpsertUserAllApi,
|
||||
UpsertUserChargelogApi,
|
||||
UserLoginApi,
|
||||
UserLogoutApi,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod _test {
|
||||
use crate::title::{MaiVersionExt, Sdgb1_50, methods::APIMethod};
|
||||
|
||||
#[test]
|
||||
fn test_obfuscate_1_50() {
|
||||
assert_eq!(
|
||||
Sdgb1_50::api_hash(APIMethod::Ping),
|
||||
"250b3482854e7697de7d8eb6ea1fabb1"
|
||||
);
|
||||
assert_eq!(
|
||||
Sdgb1_50::api_hash(APIMethod::GetUserPreviewApi),
|
||||
"004cf848f96d393a5f2720101e30b93d"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,67 @@
|
||||
use crate::title::error::ApiError;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::title::methods::APIMethod;
|
||||
|
||||
pub mod encryption;
|
||||
pub mod methods;
|
||||
pub mod model;
|
||||
|
||||
mod error;
|
||||
pub use error::ApiError;
|
||||
use nyquest::{
|
||||
Body,
|
||||
r#async::Request,
|
||||
header::{ACCEPT_ENCODING, CONTENT_ENCODING, EXPECT, USER_AGENT},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
pub trait MaiVersion {
|
||||
const AES_KEY: &[u8; 32];
|
||||
const AES_IV: &[u8; 16];
|
||||
const OBFUSECATE_PARAM: &str;
|
||||
const OBFUSECATE_SUFFIX: &str;
|
||||
const VERSION: &str;
|
||||
}
|
||||
|
||||
pub trait MaiVersionExt: MaiVersion {
|
||||
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError>;
|
||||
fn decode(data: impl AsMut<[u8]>) -> Result<Vec<u8>, ApiError>;
|
||||
|
||||
fn api_hash(api: APIMethod) -> String {
|
||||
let api_name: &str = api.into();
|
||||
|
||||
let mut md5 = md5::Context::new();
|
||||
md5.consume(api_name);
|
||||
md5.consume(Self::OBFUSECATE_SUFFIX);
|
||||
let digest = md5.finalize();
|
||||
|
||||
format!("{digest:x}")
|
||||
}
|
||||
|
||||
fn api_request<D>(
|
||||
api: APIMethod,
|
||||
agent_extra: impl Display,
|
||||
data: D,
|
||||
) -> Result<Request, ApiError>
|
||||
where
|
||||
D: Serialize,
|
||||
{
|
||||
let json = serde_json::to_vec(&data)?;
|
||||
let payload = Self::encode(json)?;
|
||||
|
||||
let api_hash = Self::api_hash(api);
|
||||
let req = Request::post(format!(
|
||||
"https://maimai-gm.wahlap.com:42081/Maimai2Servlet/{api_hash}"
|
||||
))
|
||||
.with_body(Body::json_bytes(payload))
|
||||
.with_header(USER_AGENT, format!("{api_hash}#{agent_extra}"))
|
||||
.with_header("Mai-Encoding", Self::VERSION)
|
||||
.with_header(ACCEPT_ENCODING, "")
|
||||
.with_header("Charset", "UTF-8")
|
||||
.with_header(CONTENT_ENCODING, "deflate")
|
||||
.with_header(EXPECT, "100-continue");
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sdgb1_40;
|
||||
@@ -20,10 +70,14 @@ pub struct Sdgb1_50;
|
||||
impl MaiVersion for Sdgb1_40 {
|
||||
const AES_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
|
||||
const AES_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa";
|
||||
const OBFUSECATE_PARAM: &str = "BEs2D5vW";
|
||||
const OBFUSECATE_SUFFIX: &str = "MaimaiChnBEs2D5vW";
|
||||
|
||||
const VERSION: &str = "1.40";
|
||||
}
|
||||
impl MaiVersion for Sdgb1_50 {
|
||||
const AES_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
|
||||
const AES_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
|
||||
const OBFUSECATE_PARAM: &str = "B44df8yT";
|
||||
const OBFUSECATE_SUFFIX: &str = "MaimaiChnB44df8yT";
|
||||
|
||||
const VERSION: &str = "1.50";
|
||||
}
|
||||
|
||||
4
sdgb-api/src/title/model/mod.rs
Normal file
4
sdgb-api/src/title/model/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Ping;
|
||||
Reference in New Issue
Block a user