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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
@@ -1098,10 +1104,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hmac-sha256",
|
"hmac-sha256",
|
||||||
|
"md5",
|
||||||
"nyquest",
|
"nyquest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snafu",
|
"snafu",
|
||||||
|
"strum 0.27.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1205,8 +1213,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "15bf745ed831fe29ec1ff6cc8dfb443721c08e895c9a08fcaa1e2c6f09ec020d"
|
checksum = "15bf745ed831fe29ec1ff6cc8dfb443721c08e895c9a08fcaa1e2c6f09ec020d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
"strum",
|
"strum 0.24.1",
|
||||||
"strum_macros",
|
"strum_macros 0.24.3",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1260,7 +1268,16 @@ version = "0.24.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@@ -1276,6 +1293,18 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|||||||
@@ -11,12 +11,18 @@ snafu = { workspace = true }
|
|||||||
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"] }
|
||||||
|
|
||||||
|
# other utils
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
|
|
||||||
|
# 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"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.141"
|
||||||
|
|
||||||
flate2 = "1.1.2"
|
flate2 = "1.1.2"
|
||||||
cbc = "0.1.2"
|
cbc = "0.1.2"
|
||||||
aes = "0.8.4"
|
aes = "0.8.4"
|
||||||
|
md5 = "0.8.0"
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ pub enum ApiError {
|
|||||||
PadError { error: PadError },
|
PadError { error: PadError },
|
||||||
#[snafu(display("unpad data: {error}"))]
|
#[snafu(display("unpad data: {error}"))]
|
||||||
UnpadError { error: UnpadError },
|
UnpadError { error: UnpadError },
|
||||||
|
|
||||||
#[snafu(display("io error: {source}"))]
|
#[snafu(display("io error: {source}"))]
|
||||||
#[snafu(context(false))]
|
#[snafu(context(false))]
|
||||||
IOError { source: std::io::Error },
|
IOError { source: std::io::Error },
|
||||||
|
|
||||||
|
#[snafu(display("json error: {source}"))]
|
||||||
|
#[snafu(context(false))]
|
||||||
|
JSONError { source: serde_json::Error },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UnpadError> for ApiError {
|
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 encryption;
|
||||||
|
pub mod methods;
|
||||||
|
pub mod model;
|
||||||
|
|
||||||
mod error;
|
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 {
|
pub trait MaiVersion {
|
||||||
const AES_KEY: &[u8; 32];
|
const AES_KEY: &[u8; 32];
|
||||||
const AES_IV: &[u8; 16];
|
const AES_IV: &[u8; 16];
|
||||||
const OBFUSECATE_PARAM: &str;
|
const OBFUSECATE_SUFFIX: &str;
|
||||||
|
const VERSION: &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MaiVersionExt: MaiVersion {
|
pub trait MaiVersionExt: MaiVersion {
|
||||||
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError>;
|
fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, ApiError>;
|
||||||
fn decode(data: impl AsMut<[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;
|
pub struct Sdgb1_40;
|
||||||
@@ -20,10 +70,14 @@ pub struct Sdgb1_50;
|
|||||||
impl MaiVersion for Sdgb1_40 {
|
impl MaiVersion for Sdgb1_40 {
|
||||||
const AES_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
|
const AES_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
|
||||||
const AES_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa";
|
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 {
|
impl MaiVersion for Sdgb1_50 {
|
||||||
const AES_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
|
const AES_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
|
||||||
const AES_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
|
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)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
|
Ping,
|
||||||
/// Login with QRCode from wechat
|
/// Login with QRCode from wechat
|
||||||
QRLogin {
|
QRLogin {
|
||||||
/// content of the qrcode, only the last 64 characters were used
|
/// content of the qrcode, only the last 64 characters were used
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use nyquest_preset::nyquest::ClientBuilder;
|
use nyquest_preset::nyquest::ClientBuilder;
|
||||||
use palc::Parser;
|
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 spdlog::{error, info};
|
||||||
|
|
||||||
use crate::commands::Cli;
|
use crate::commands::Cli;
|
||||||
@@ -15,6 +18,12 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
|
|||||||
let client = ClientBuilder::default().build_async().await?;
|
let client = ClientBuilder::default().build_async().await?;
|
||||||
|
|
||||||
match cmd.command {
|
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 } => {
|
commands::Commands::QRLogin { ref qrcode_content } => {
|
||||||
let qrcode = QRCode { qrcode_content };
|
let qrcode = QRCode { qrcode_content };
|
||||||
match qrcode.login(&client).await {
|
match qrcode.login(&client).await {
|
||||||
|
|||||||
Reference in New Issue
Block a user