diff --git a/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/get_user_auth_details.rs b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/get_user_auth_details.rs new file mode 100644 index 0000000000..8b970b1432 --- /dev/null +++ b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/get_user_auth_details.rs @@ -0,0 +1,142 @@ +use crate::creds::seedance2pro_session::Seedance2ProSession; +use crate::error::seedance2pro_client_error::Seedance2ProClientError; +use crate::error::seedance2pro_error::Seedance2ProError; +use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError; +use crate::requests::get_user_auth_details::request_types::*; +use crate::requests::kinovi_host::{KinoviHost, resolve_host}; +use crate::utils::common_headers::FIREFOX_USER_AGENT; +use log::info; +use wreq::Client; +use wreq_util::Emulation; + +// --- Args & response --- + +pub struct GetUserAuthDetailsArgs<'a> { + pub session: &'a Seedance2ProSession, + + /// Override the default host (kinovi.ai). + pub host_override: Option, +} + +#[derive(Debug)] +pub struct GetUserAuthDetailsResponse { + pub email: String, + pub credits: u64, + pub available_credits: u64, +} + +// --- Implementation --- + +/// The tRPC `input` query param for `auth.user` — a static null payload. +const AUTH_USER_INPUT: &str = + r#"{"0":{"json":null,"meta":{"values":["undefined"],"v":1}}}"#; + +pub async fn get_user_auth_details( + args: GetUserAuthDetailsArgs<'_>, +) -> Result { + let host = resolve_host(args.host_override.as_ref()); + let base_url = host.api_base_url(); + let url = format!("{}/api/trpc/auth.user", base_url); + + info!("Fetching user auth details..."); + + let client = Client::builder() + .emulation(Emulation::Firefox143) + .build() + .map_err(|err| Seedance2ProClientError::WreqClientError(err))?; + + let cookie = args.session.cookies.as_str(); + let referer = format!("{}/pricing", base_url); + + let request = client.get(&url) + .query(&[("batch", "1"), ("input", AUTH_USER_INPUT)]) + .header("User-Agent", FIREFOX_USER_AGENT) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.9") + .header("Accept-Encoding", "gzip, deflate, br, zstd") + .header("Referer", &referer) + .header("content-type", "application/json") + .header("x-trpc-source", "client") + .header("Connection", "keep-alive") + .header("Cookie", cookie) + .header("Sec-Fetch-Dest", "empty") + .header("Sec-Fetch-Mode", "cors") + .header("Sec-Fetch-Site", "same-origin") + .header("Priority", "u=4") + .header("TE", "trailers") + .build() + .map_err(|err| Seedance2ProClientError::WreqClientError(err))?; + + let response = client.execute(request) + .await + .map_err(|err| Seedance2ProGenericApiError::WreqError(err))?; + + let status = response.status(); + let response_body = response.text() + .await + .map_err(|err| Seedance2ProGenericApiError::WreqError(err))?; + + info!("Get user auth details response status: {}", status); + + if !status.is_success() { + return Err(Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody { + status_code: status, + body: response_body, + }.into()); + } + + let batch_response: Vec = serde_json::from_str(&response_body) + .map_err(|err| Seedance2ProGenericApiError::SerdeResponseParseErrorWithBody( + err, + response_body.clone(), + ))?; + + let json = batch_response + .into_iter() + .next() + .ok_or_else(|| Seedance2ProGenericApiError::UncategorizedBadResponse( + "Empty batch response array".to_string() + ))? + .result + .data + .json; + + info!("User: {}, credits: {}, available: {}", json.email, json.credits, json.available_credits); + + Ok(GetUserAuthDetailsResponse { + email: json.email, + credits: json.credits, + available_credits: json.available_credits, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::creds::seedance2pro_session::Seedance2ProSession; + use crate::test_utils::get_test_cookies::get_test_cookies; + use crate::test_utils::setup_test_logging::setup_test_logging; + use errors::AnyhowResult; + use log::LevelFilter; + + fn test_session() -> AnyhowResult { + let cookies = get_test_cookies()?; + Ok(Seedance2ProSession::from_cookies_string(cookies)) + } + + #[tokio::test] + #[ignore] // manually test — requires real cookies + async fn test_get_user_auth_details() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + let result = get_user_auth_details(GetUserAuthDetailsArgs { + session: &session, + host_override: None, + }).await?; + println!("Email: {}", result.email); + println!("Credits: {}", result.credits); + println!("Available credits: {}", result.available_credits); + assert_eq!(1, 2); // NB: Intentional failure to inspect output. + Ok(()) + } +} diff --git a/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/mod.rs b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/mod.rs new file mode 100644 index 0000000000..6277520a72 --- /dev/null +++ b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/mod.rs @@ -0,0 +1,2 @@ +pub mod get_user_auth_details; +mod request_types; diff --git a/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/request_types.rs b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/request_types.rs new file mode 100644 index 0000000000..46412eaafd --- /dev/null +++ b/crates/api_clients/seedance2pro_client/src/requests/get_user_auth_details/request_types.rs @@ -0,0 +1,24 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize, Debug)] +pub(super) struct BatchResponseItem { + pub result: BatchResponseResult, +} + +#[derive(Deserialize, Debug)] +pub(super) struct BatchResponseResult { + pub data: BatchResponseData, +} + +#[derive(Deserialize, Debug)] +pub(super) struct BatchResponseData { + pub json: RawUserAuthDetails, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(super) struct RawUserAuthDetails { + pub email: String, + pub credits: u64, + pub available_credits: u64, +} diff --git a/crates/api_clients/seedance2pro_client/src/requests/mod.rs b/crates/api_clients/seedance2pro_client/src/requests/mod.rs index 767c04fc68..0559c195e4 100644 --- a/crates/api_clients/seedance2pro_client/src/requests/mod.rs +++ b/crates/api_clients/seedance2pro_client/src/requests/mod.rs @@ -1,5 +1,6 @@ pub mod generate_character; pub mod generate_video; +pub mod get_user_auth_details; pub mod kinovi_host; pub mod poll_characters; pub mod poll_orders;