error categorization

This commit is contained in:
Brandon Thomas
2025-10-31 04:19:41 -04:00
parent 706bdb8d83
commit 02eafa96cc
9 changed files with 99 additions and 39 deletions
@@ -0,0 +1 @@
{"error":{"code":7,"message":"Request rejected by anti-bot rules.","details":[]}}
@@ -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()
}
@@ -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),
@@ -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."),
@@ -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;
pub mod grok_specific_api_error;
pub (crate) mod categorize_grok_http_error;
@@ -122,7 +122,7 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(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 {
@@ -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<GrokVideoGenChatConversationResponse, GrokError> {
pub async fn wait_for_video(&self) -> Result<GrokVideoGenChatConversationResponse, GrokError> {
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<Response, GrokGenericApiError> {
Ok(client.execute(request)
async fn send_request(client: &Client, request: Request) -> Result<Response, GrokError> {
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);
@@ -0,0 +1,7 @@
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
@@ -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 {
/*
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
*/
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(())