From beb15d83233061ffbaf8a8396d58b136990100ab Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 20 Feb 2026 06:57:01 +0700 Subject: [PATCH] cursoring --- .../src/requests/poll_orders/poll_orders.rs | 99 +++++++++++++++---- .../src/requests/poll_orders/request_types.rs | 3 +- 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/crates/api_clients/seedance2pro/src/requests/poll_orders/poll_orders.rs b/crates/api_clients/seedance2pro/src/requests/poll_orders/poll_orders.rs index 48b15c55f1..efe7ebf1b7 100644 --- a/crates/api_clients/seedance2pro/src/requests/poll_orders/poll_orders.rs +++ b/crates/api_clients/seedance2pro/src/requests/poll_orders/poll_orders.rs @@ -9,24 +9,38 @@ use wreq_util::Emulation; const GET_ORDERS_BASE_URL: &str = "https://seedance2-pro.com/api/trpc/userOrder.getOrders"; -/// The fixed tRPC input for fetching recent orders (limit 30, forward direction). -const ORDERS_INPUT_JSON: &str = - r#"{"0":{"json":{"limit":30,"format":null,"direction":"forward"},"meta":{"values":{"format":["undefined"]},"v":1}}}"#; - const FIREFOX_USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0"; +/// Builds the tRPC `input` JSON for the getOrders endpoint. +/// When `cursor` is `Some`, it is included in the JSON payload. +fn build_input_json(cursor: Option) -> String { + match cursor { + None => r#"{"0":{"json":{"limit":30,"format":null,"direction":"forward"},"meta":{"values":{"format":["undefined"]},"v":1}}}"#.to_string(), + Some(c) => format!( + r#"{{"0":{{"json":{{"limit":30,"format":null,"cursor":{cursor},"direction":"forward"}},"meta":{{"values":{{"format":["undefined"]}},"v":1}}}}}}"#, + cursor = c + ), + } +} // --- Args & response --- pub struct PollOrdersArgs<'a> { pub session: &'a Seedance2ProSession, + + /// Optional cursor from a previous `PollOrdersResponse::next_cursor`. + /// When `None`, the most recent orders are returned. + pub cursor: Option, } pub struct PollOrdersResponse { pub orders: Vec, -} + /// Present when there are more orders to fetch. + /// Pass this value as `PollOrdersArgs::cursor` in the next call. + pub next_cursor: Option, +} // --- Public types --- @@ -90,10 +104,13 @@ pub struct OrderStatus { /// ISO 8601 creation timestamp (e.g. `"2026-02-19T01:20:50.398Z"`). pub created_at: String, } + // --- Implementation --- pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result { - info!("Polling orders..."); + info!("Polling orders (cursor: {:?})...", args.cursor); + + let input_json = build_input_json(args.cursor); let client = Client::builder() .emulation(Emulation::Firefox143) @@ -103,7 +120,7 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result) -> Result = serde_json::from_str(&response_body) .map_err(|err| Seedance2ProGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.clone()))?; - let raw_orders = batch_response + let json = batch_response .into_iter() .next() .ok_or_else(|| Seedance2ProGenericApiError::UncategorizedBadResponse( @@ -147,10 +164,11 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result = raw_orders + let next_cursor = json.next_cursor; + + let orders: Vec = json.orders .into_iter() .map(|o| OrderStatus { order_id: o.order_id, @@ -167,9 +185,9 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result AnyhowResult<()> { setup_test_logging(LevelFilter::Trace); let session = test_session()?; - let args = PollOrdersArgs { - session: &session, - }; - let result = poll_orders(args).await?; + let result = poll_orders(PollOrdersArgs { session: &session, cursor: None }).await?; println!("Orders returned: {}", result.orders.len()); + println!("Next cursor: {:?}", result.next_cursor); for order in &result.orders { println!(" {} | {:?} | result_url={:?} | fail={:?}", order.order_id, order.task_status, order.result_url, order.fail_reason); @@ -204,4 +220,53 @@ mod tests { Ok(()) } + #[tokio::test] + #[ignore] // manually test — requires real cookies and a known cursor value + async fn test_poll_with_cursor() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + // Use the cursor value returned from a prior call (e.g. 394062 from the example responses). + let cursor: u64 = 394062; + let result = poll_orders(PollOrdersArgs { session: &session, cursor: Some(cursor) }).await?; + println!("Orders returned (with cursor {}): {}", cursor, result.orders.len()); + println!("Next cursor: {:?}", result.next_cursor); + for order in &result.orders { + println!(" {} | {:?} | result_url={:?} | fail={:?}", + order.order_id, order.task_status, order.result_url, order.fail_reason); + } + assert_eq!(1, 2); // NB: Intentional failure to inspect output. + Ok(()) + } + + #[tokio::test] + #[ignore] // manually test — requires real cookies; exhausts all pages + async fn test_poll_all_pages() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + + let mut cursor: Option = None; + let mut page = 0usize; + let mut total_orders = 0usize; + + loop { + page += 1; + let result = poll_orders(PollOrdersArgs { session: &session, cursor }).await?; + let page_count = result.orders.len(); + total_orders += page_count; + + println!("Page {}: {} orders, next_cursor: {:?}", page, page_count, result.next_cursor); + for order in &result.orders { + println!(" {} | {:?}", order.order_id, order.task_status); + } + + cursor = result.next_cursor; + if cursor.is_none() { + break; + } + } + + println!("Total orders across {} pages: {}", page, total_orders); + assert_eq!(1, 2); // NB: Intentional failure to inspect output. + Ok(()) + } } diff --git a/crates/api_clients/seedance2pro/src/requests/poll_orders/request_types.rs b/crates/api_clients/seedance2pro/src/requests/poll_orders/request_types.rs index 73f5a3192c..0fcea71b72 100644 --- a/crates/api_clients/seedance2pro/src/requests/poll_orders/request_types.rs +++ b/crates/api_clients/seedance2pro/src/requests/poll_orders/request_types.rs @@ -19,8 +19,7 @@ pub(super) struct BatchResponseData { #[serde(rename_all = "camelCase")] pub(super) struct OrdersResponseJson { pub orders: Vec, - #[allow(dead_code)] - pub next_cursor: Option, + pub next_cursor: Option, } #[derive(Deserialize, Debug)]