feat: initial support of method call

This commit is contained in:
mokurin000
2025-07-30 02:33:31 +08:00
parent d870dc7047
commit 6ee009715d
8 changed files with 179 additions and 8 deletions

View File

@@ -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 {

View 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"
);
}
}

View File

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

View File

@@ -0,0 +1,4 @@
use serde::Serialize;
#[derive(Serialize)]
pub struct Ping;