feat: initial support of method call
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -718,6 +718,12 @@ version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
@@ -1098,10 +1104,12 @@ dependencies = [
|
||||
"digest",
|
||||
"flate2",
|
||||
"hmac-sha256",
|
||||
"md5",
|
||||
"nyquest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
"strum 0.27.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1205,8 +1213,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15bf745ed831fe29ec1ff6cc8dfb443721c08e895c9a08fcaa1e2c6f09ec020d"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"strum 0.24.1",
|
||||
"strum_macros 0.24.3",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
@@ -1260,7 +1268,16 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
dependencies = [
|
||||
"strum_macros 0.27.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1276,6 +1293,18 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
||||
@@ -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;
|
||||
@@ -10,6 +10,7 @@ pub struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
Ping,
|
||||
/// Login with QRCode from wechat
|
||||
QRLogin {
|
||||
/// content of the qrcode, only the last 64 characters were used
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use nyquest_preset::nyquest::ClientBuilder;
|
||||
use palc::Parser;
|
||||
use sdgb_api::all_net::QRCode;
|
||||
use sdgb_api::{
|
||||
all_net::QRCode,
|
||||
title::{MaiVersionExt, Sdgb1_50, methods::APIMethod, model::Ping},
|
||||
};
|
||||
use spdlog::{error, info};
|
||||
|
||||
use crate::commands::Cli;
|
||||
@@ -15,6 +18,12 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
||||
let client = ClientBuilder::default().build_async().await?;
|
||||
|
||||
match cmd.command {
|
||||
commands::Commands::Ping => {
|
||||
let req = Sdgb1_50::api_request(APIMethod::Ping, "", Ping)?;
|
||||
let data = client.request(req).await?.bytes().await?;
|
||||
let decoded = Sdgb1_50::decode(data)?;
|
||||
info!("resp: {:?}", String::from_utf8_lossy(&decoded));
|
||||
}
|
||||
commands::Commands::QRLogin { ref qrcode_content } => {
|
||||
let qrcode = QRCode { qrcode_content };
|
||||
match qrcode.login(&client).await {
|
||||
|
||||
Reference in New Issue
Block a user