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, ApiError>; fn decode(data: impl AsMut<[u8]>) -> Result, 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( api: APIMethod, agent_extra: impl Display, data: D, ) -> Result 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( client: &AsyncClient, api: APIMethod, agent_extra: impl Display, data: D, ) -> impl Future, ApiError>> where D: Serialize, { async { let req = Self::api_request(api, agent_extra, data)?; let data = client.request(req).await?.bytes().await?; let decoded = Self::decode(data)?; Ok(decoded) } } fn request( client: &AsyncClient, api: APIMethod, agent_extra: impl Display, data: D, ) -> impl Future> where D: Serialize, 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