122 lines
3.4 KiB
Rust
122 lines
3.4 KiB
Rust
use std::fmt::Display;
|
|
|
|
use crate::title::methods::APIMethod;
|
|
|
|
pub mod encryption;
|
|
pub mod methods;
|
|
pub mod model;
|
|
|
|
use super::ApiError;
|
|
|
|
use nyquest::{
|
|
AsyncClient, Body,
|
|
r#async::Request,
|
|
header::{ACCEPT_ENCODING, CONTENT_ENCODING, EXPECT, USER_AGENT},
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub trait MaiVersion {
|
|
const AES_KEY: &[u8; 32];
|
|
const AES_IV: &[u8; 16];
|
|
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_call<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)
|
|
}
|
|
|
|
fn request_raw<D>(
|
|
client: &AsyncClient,
|
|
api: APIMethod,
|
|
agent_extra: impl Display + Send + 'static,
|
|
data: D,
|
|
) -> impl Future<Output = Result<Vec<u8>, ApiError>>
|
|
where
|
|
D: Serialize + Send + 'static,
|
|
{
|
|
#[cfg(feature = "compio")]
|
|
use compio::runtime::spawn_blocking;
|
|
#[cfg(feature = "tokio")]
|
|
use tokio::task::spawn_blocking;
|
|
|
|
async {
|
|
let req = spawn_blocking(move || Self::api_call(api, agent_extra, data))
|
|
.await
|
|
.map_err(|_| ApiError::JoinError)??;
|
|
let data = client.request(req).await?.bytes().await?;
|
|
let decoded = spawn_blocking(move || Self::decode(data))
|
|
.await
|
|
.map_err(|_| ApiError::JoinError)??;
|
|
Ok(decoded)
|
|
}
|
|
}
|
|
|
|
fn request<D, R>(
|
|
client: &AsyncClient,
|
|
api: APIMethod,
|
|
agent_extra: impl Display + Send + 'static,
|
|
data: D,
|
|
) -> impl Future<Output = Result<R, ApiError>>
|
|
where
|
|
D: Serialize + Send + 'static,
|
|
R: for<'a> Deserialize<'a>,
|
|
{
|
|
async {
|
|
let raw_data = Self::request_raw(client, api, agent_extra, data).await?;
|
|
Ok(serde_json::from_slice(&raw_data)?)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Sdgb1_40;
|
|
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_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_SUFFIX: &str = "MaimaiChnB44df8yT";
|
|
|
|
const VERSION: &str = "1.50";
|
|
}
|