From 02eafa96ccf389094d2216efe4601b11ef8a49c8 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 31 Oct 2025 04:19:41 -0400 Subject: [PATCH] error categorization --- .../error/_metadata/anti_bot_http_failure.txt | 1 + .../src/error/categorize_grok_http_error.rs | 37 +++++++++++++++ .../src/error/grok_generic_api_error.rs | 4 ++ .../src/error/grok_specific_api_error.rs | 10 ++++- .../api_clients/grok_client/src/error/mod.rs | 3 +- .../upload_image_and_generate_video.rs | 2 +- .../grok_video_gen_chat_conversation.rs | 29 +++++++++--- .../cloudflare_301_response_body.txt | 7 +++ .../src/filter_cloudflare_errors.rs | 45 +++++++------------ 9 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 crates/api_clients/grok_client/src/error/_metadata/anti_bot_http_failure.txt create mode 100644 crates/api_clients/grok_client/src/error/categorize_grok_http_error.rs create mode 100644 crates/lib/cloudflare_errors/src/_metadata/cloudflare_301_response_body.txt diff --git a/crates/api_clients/grok_client/src/error/_metadata/anti_bot_http_failure.txt b/crates/api_clients/grok_client/src/error/_metadata/anti_bot_http_failure.txt new file mode 100644 index 0000000000..85c20c57f9 --- /dev/null +++ b/crates/api_clients/grok_client/src/error/_metadata/anti_bot_http_failure.txt @@ -0,0 +1 @@ +{"error":{"code":7,"message":"Request rejected by anti-bot rules.","details":[]}} diff --git a/crates/api_clients/grok_client/src/error/categorize_grok_http_error.rs b/crates/api_clients/grok_client/src/error/categorize_grok_http_error.rs new file mode 100644 index 0000000000..4d84b63918 --- /dev/null +++ b/crates/api_clients/grok_client/src/error/categorize_grok_http_error.rs @@ -0,0 +1,37 @@ +use crate::error::grok_error::GrokError; +use crate::error::grok_generic_api_error::GrokGenericApiError; +use crate::error::grok_specific_api_error::GrokSpecificApiError; +use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors; +use wreq::StatusCode; + +/// We know the response is an error at this point. +/// We're just turning it into the right error. +pub fn categorize_grok_http_error(status_code: StatusCode, maybe_body: Option<&str>) -> GrokError { + + if let Some(body) = maybe_body { + if let Err(err) = filter_cloudflare_errors(status_code.as_u16(), body) { + return GrokGenericApiError::CloudflareError(err).into(); + } + + let body = body.to_lowercase(); + let anti_bot = body.contains("anti-bot") || body.contains("rejected"); + + if anti_bot { + return GrokSpecificApiError::AutomationBlocked.into(); + } + + match status_code { + StatusCode::TOO_MANY_REQUESTS => { + return GrokSpecificApiError::TooManyVideos.into(); + } + _ => {}, + } + + return GrokGenericApiError::UncategorizedBadResponseWithStatusAndBody { + status_code, + body: body.to_string(), + }.into(); + } + + GrokGenericApiError::UncategorizedBadResponseWithStatus(status_code).into() +} diff --git a/crates/api_clients/grok_client/src/error/grok_generic_api_error.rs b/crates/api_clients/grok_client/src/error/grok_generic_api_error.rs index f14a74062e..a56d0f3ab2 100644 --- a/crates/api_clients/grok_client/src/error/grok_generic_api_error.rs +++ b/crates/api_clients/grok_client/src/error/grok_generic_api_error.rs @@ -40,6 +40,9 @@ pub enum GrokGenericApiError { /// Specifically, this is for non-200 (error) responses. SerdeParseErrorWithBodyOnNon200(serde_json::Error, String), + /// An uncategorized bad HTTP response from Grok. + UncategorizedBadResponseWithStatus(StatusCode), + /// An uncategorized bad HTTP response from Grok. UncategorizedBadResponseWithStatusAndBody { status_code: StatusCode, @@ -69,6 +72,7 @@ impl Display for GrokGenericApiError { Self::UnexpectedWebsocketUpgradeStatusCode(status) => write!(f, "Unexpected websocket upgrade status code: {}", status), Self::SerdeResponseParseErrorWithBody(err, body) => write!(f, "Failed to parse response body: {:?}. Body: {}", err, body), Self::SerdeParseErrorWithBodyOnNon200(err, body) => write!(f, "Failed to parse non-200 response body: {:?}. Body: {}", err, body), + Self::UncategorizedBadResponseWithStatus(status) => write!(f, "Uncategorized with status code: {}", status), Self::UncategorizedBadResponseWithStatusAndBody { status_code, body } => write!(f, "Uncategorized bad response: status code {}, body: {}", status_code, body), Self::UploadFailed => write!(f, "Upload failed"), Self::WreqWebsocketUpgradeError(err) => write!(f, "Websocket upgrade error: {}", err), diff --git a/crates/api_clients/grok_client/src/error/grok_specific_api_error.rs b/crates/api_clients/grok_client/src/error/grok_specific_api_error.rs index 611f0950b8..e0fd7e4078 100644 --- a/crates/api_clients/grok_client/src/error/grok_specific_api_error.rs +++ b/crates/api_clients/grok_client/src/error/grok_specific_api_error.rs @@ -3,7 +3,12 @@ use std::fmt::{Display, Formatter}; #[derive(Debug)] pub enum GrokSpecificApiError { - TodoError, + /// Grok is detecting and blocking our bot, likely due to new secrets use. + AutomationBlocked, + + /// Too many videos are being generated. + TooManyVideos, + // /// Failed to generate a bearer token despite the `200 OK` response. // /// This has been generated by sending old/expired session cookies or empty cookies. @@ -55,7 +60,8 @@ impl Error for GrokSpecificApiError {} impl Display for GrokSpecificApiError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::TodoError => write!(f, "TodoError"), + Self::AutomationBlocked => write!(f, "Automation blocked"), + Self::TooManyVideos => write!(f, "Too many videos are being generated"), //Self::FailedToGenerateBearerToken => write!(f, "Failed to generate bearer token. Probably invalid or expired session cookies."), //Self::InvalidJwt => write!(f, "Invalid JWT token. Please refresh the token."), diff --git a/crates/api_clients/grok_client/src/error/mod.rs b/crates/api_clients/grok_client/src/error/mod.rs index 2084db69cf..97c7749fe7 100644 --- a/crates/api_clients/grok_client/src/error/mod.rs +++ b/crates/api_clients/grok_client/src/error/mod.rs @@ -1,4 +1,5 @@ pub mod grok_client_error; pub mod grok_error; pub mod grok_generic_api_error; -pub mod grok_specific_api_error; \ No newline at end of file +pub mod grok_specific_api_error; +pub (crate) mod categorize_grok_http_error; \ No newline at end of file diff --git a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs index 5da136d57c..187e5aa183 100644 --- a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs +++ b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs @@ -122,7 +122,7 @@ pub async fn upload_image_and_generate_video>(args: UploadImageAn if args.wait_for_generation { info!("Waiting for video generation..."); - let video_gen_result = request.send().await?; + let video_gen_result = request.wait_for_video().await?; maybe_video_file_id = video_gen_result.video_file_id; generation_is_complete = true; } else { diff --git a/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs b/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs index ed294841e8..6aa6913c83 100644 --- a/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs +++ b/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs @@ -6,6 +6,7 @@ use crate::datatypes::api::svg_path_data::SvgPathData; use crate::datatypes::api::user_id::UserId; use crate::datatypes::api::verification_token::VerificationToken; use crate::datatypes::api::xsid_numbers::XsidNumbers; +use crate::error::categorize_grok_http_error::categorize_grok_http_error; use crate::error::grok_client_error::GrokClientError; use crate::error::grok_error::GrokError; use crate::error::grok_generic_api_error::GrokGenericApiError; @@ -111,7 +112,7 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> { } - pub async fn send(&self) -> Result { + pub async fn wait_for_video(&self) -> Result { info!("Configuring client and request..."); let client = Self::build_client()?; let http_request = self.build_request(&client)?; @@ -139,7 +140,7 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> { /// TODO: Handle anti-bot detection /// Body: {"error":{"code":7,"message":"Request rejected by anti-bot rules.","details":[]}} - let response_body = &response.text() + let response_body = response.text() .await .map_err(|err| { error!("Error reading Grok create media response body: {:?}", err); @@ -266,13 +267,29 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> { Ok(http_request) } - async fn send_request(client: &Client, request: Request) -> Result { - Ok(client.execute(request) + async fn send_request(client: &Client, request: Request) -> Result { + let response = client.execute(request) .await .map_err(|err| { error!("Error during create media: {:?}", err); GrokGenericApiError::WreqError(err) - })?) + })?; + + let status = response.status(); + + if !status.is_success() { + let maybe_body = response.text() + .await + .map_err(|err| { + error!("Error reading Grok video generation response body: {:?}", err); + err + }) + .ok(); + + return Err(categorize_grok_http_error(status, maybe_body.as_deref())) + } + + Ok(response) } } @@ -335,7 +352,7 @@ mod tests { request_timeout: None, }; - let result = request.send().await?; + let result = request.wait_for_video().await?; println!("Result: {:?}", result); diff --git a/crates/lib/cloudflare_errors/src/_metadata/cloudflare_301_response_body.txt b/crates/lib/cloudflare_errors/src/_metadata/cloudflare_301_response_body.txt new file mode 100644 index 0000000000..0bb46277be --- /dev/null +++ b/crates/lib/cloudflare_errors/src/_metadata/cloudflare_301_response_body.txt @@ -0,0 +1,7 @@ + +301 Moved Permanently + +

301 Moved Permanently

+
cloudflare
+ + diff --git a/crates/lib/cloudflare_errors/src/filter_cloudflare_errors.rs b/crates/lib/cloudflare_errors/src/filter_cloudflare_errors.rs index c8d10ba5c7..a0c1e58485 100644 --- a/crates/lib/cloudflare_errors/src/filter_cloudflare_errors.rs +++ b/crates/lib/cloudflare_errors/src/filter_cloudflare_errors.rs @@ -22,39 +22,26 @@ pub fn filter_cloudflare_errors(status_code: u16, body: &str) -> Result<(), Clou let is_cloudflare = body.contains("cloudflare") || body.contains("Cloudflare"); - if status_code == 301 { - /* - - 301 Moved Permanently - -

301 Moved Permanently

-
cloudflare
- - - */ - if is_cloudflare { - // TODO: Include location header. - return Err(CloudflareError::MovedPermanently301); - } - } - // let is_cloudflare = body.contains("cloudflare.com") // || body.contains("Cloudflare Ray ID"); - if is_cloudflare { - match status_code { - 502 => return Err(CloudflareError::BadGateway502), - 504 => return Err(CloudflareError::GatewayTimeout504), - 524 => return Err(CloudflareError::TimeoutOccurred524), - _ => {}, // Fall-through - } + if !is_cloudflare { + return Ok(()); + } - if body.contains("errorcode_504") - || body.contains("Gateway time-out") - || body.contains("Error code 504") - { - return Err(CloudflareError::GatewayTimeout504); - } + match status_code { + 301 => return Err(CloudflareError::MovedPermanently301), // TODO: Include location header. + 502 => return Err(CloudflareError::BadGateway502), + 504 => return Err(CloudflareError::GatewayTimeout504), + 524 => return Err(CloudflareError::TimeoutOccurred524), + _ => {}, + } + + if body.contains("errorcode_504") + || body.contains("Gateway time-out") + || body.contains("Error code 504") + { + return Err(CloudflareError::GatewayTimeout504); } Ok(())