mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-08 11:01:35 +00:00
first request wip, headers crate
This commit is contained in:
1 parent
0bcfb774c0
commit
53cd4618eb
34 files changed
+26885
-6
No files matched your search
Generated
+9
@@ -5151,6 +5151,14 @@ dependencies = [
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_headers"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"errors",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_server_common"
|
||||
version = "0.0.1"
|
||||
@@ -14182,6 +14190,7 @@ dependencies = [
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.31",
|
||||
"http_headers",
|
||||
"log",
|
||||
"log4rs",
|
||||
"once_cell",
|
||||
|
||||
@@ -68,6 +68,7 @@ members = [
|
||||
"crates/lib/files/media",
|
||||
"crates/lib/files/mimetypes",
|
||||
"crates/lib/files/videos",
|
||||
"crates/lib/http_headers",
|
||||
"crates/lib/idempotency",
|
||||
"crates/lib/image_algorithms",
|
||||
"crates/lib/jobs_common",
|
||||
@@ -200,6 +201,7 @@ fal_client = { path = "crates/api_clients/fal_client" }
|
||||
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
grok_client = { path = "crates/api_clients/grok_client" }
|
||||
http_headers = { path = "crates/lib/http_headers" }
|
||||
idempotency = { path = "crates/lib/idempotency" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
jwt_light = { path = "crates/lib/jwt_light" }
|
||||
|
||||
@@ -20,6 +20,7 @@ cloudflare_errors.workspace = true
|
||||
cloudflare_mitigation.workspace = true
|
||||
cookie_store.workspace = true
|
||||
errors.workspace = true
|
||||
http_headers.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod world_labs_bearer_token;
|
||||
pub mod world_labs_cookies;
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WorldLabsBearerToken {
|
||||
/// The bearer token
|
||||
/// Without "Authorization:" header name and without "Bearer(space)" prefix.
|
||||
bearer_token: String,
|
||||
}
|
||||
|
||||
impl WorldLabsBearerToken {
|
||||
pub fn new(mut bearer_token: String) -> Self {
|
||||
if bearer_token.starts_with("Bearer") {
|
||||
bearer_token = bearer_token
|
||||
.trim_start_matches("Bearer")
|
||||
.trim()
|
||||
.to_string();
|
||||
}
|
||||
Self { bearer_token }
|
||||
}
|
||||
|
||||
pub fn as_raw_str(&self) -> &str {
|
||||
&self.bearer_token
|
||||
}
|
||||
|
||||
pub fn as_raw_bytes(&self) -> &[u8] {
|
||||
self.bearer_token.as_bytes()
|
||||
}
|
||||
|
||||
pub fn to_raw_string(&self) -> String {
|
||||
self.bearer_token.clone()
|
||||
}
|
||||
|
||||
pub fn to_bearer_token_string(&self) -> String {
|
||||
format!("Bearer {}", self.bearer_token)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"error":{"code":7,"message":"Request rejected by anti-bot rules.","details":[]}}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::error::world_labs_error::WorldLabsError;
|
||||
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
|
||||
use crate::error::world_labs_specific_api_error::WorldLabsSpecificApiError;
|
||||
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>) -> WorldLabsError {
|
||||
|
||||
if let Some(body) = maybe_body {
|
||||
if let Err(err) = filter_cloudflare_errors(status_code.as_u16(), body) {
|
||||
return WorldLabsGenericApiError::CloudflareError(err).into();
|
||||
}
|
||||
|
||||
let body = body.to_lowercase();
|
||||
let anti_bot = body.contains("anti-bot") || body.contains("rejected");
|
||||
|
||||
if anti_bot {
|
||||
return WorldLabsSpecificApiError::AutomationBlocked.into();
|
||||
}
|
||||
|
||||
match status_code {
|
||||
StatusCode::TOO_MANY_REQUESTS => {
|
||||
return WorldLabsSpecificApiError::TooManyVideos.into();
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
return WorldLabsGenericApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code,
|
||||
body: body.to_string(),
|
||||
}.into();
|
||||
}
|
||||
|
||||
WorldLabsGenericApiError::UncategorizedBadResponseWithStatus(status_code).into()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod world_labs_client_error;
|
||||
pub mod world_labs_error;
|
||||
pub mod world_labs_generic_api_error;
|
||||
pub mod world_labs_specific_api_error;
|
||||
pub (crate) mod categorize_world_labs_http_error;
|
||||
@@ -0,0 +1,154 @@
|
||||
use base64::DecodeError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorldLabsClientError {
|
||||
|
||||
/// An error was encountered in building the Wreq client
|
||||
WreqClientError(wreq::Error),
|
||||
|
||||
/// Error serializing a message to send the websocket
|
||||
WebsocketRequestSerializationError(serde_json::Error),
|
||||
|
||||
/// Error locking the websocket for sending/receiving
|
||||
WebsocketLockError,
|
||||
|
||||
/// Error reading from a websocket.
|
||||
WebsocketReadError(wreq::Error),
|
||||
|
||||
/// Error sending to an open websocket.
|
||||
WebsocketSendError(wreq::Error),
|
||||
|
||||
/// Can't open a local file for uploading.
|
||||
CannotOpenLocalFileForUpload(std::io::Error),
|
||||
|
||||
/// Can't read a local file for uploading.
|
||||
CannotReadLocalFileForUpload(std::io::Error),
|
||||
|
||||
/// Couldn't write to the file
|
||||
CannotOpenLocalFileForWriting(std::io::Error),
|
||||
|
||||
/// Couldn't write to the file
|
||||
CannotWriteLocalFile(std::io::Error),
|
||||
|
||||
/// File for upload has an invalid path.
|
||||
FileForUploadHasInvalidPath,
|
||||
|
||||
/// Error parsing HTML
|
||||
HtmlParsingError,
|
||||
|
||||
/// Error decoding verification token
|
||||
FailedToDecodeVerificationToken(DecodeError),
|
||||
|
||||
/// Verification token bytes are invalid
|
||||
InvalidVerificationTokenBytes,
|
||||
|
||||
/// Our script logic is out of date
|
||||
ScriptLogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (SVG)
|
||||
ScriptSvgLogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (script 1)
|
||||
Script1LogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (script 2)
|
||||
Script2LogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (xsid script)
|
||||
ScriptXsidLogicOutOfDate,
|
||||
|
||||
/// Signature algorithm isn't working with inputs
|
||||
BadSignatureInputs,
|
||||
|
||||
/// Something is broken with timeout math
|
||||
TimeoutMathBroken,
|
||||
|
||||
/// Can't make request because cookies aren't present
|
||||
NoCookiesPresent,
|
||||
|
||||
/// Unknown error generating video
|
||||
ErrorGeneratingVideo,
|
||||
|
||||
// /// An error reading the file for upload.
|
||||
// FileForUploadReadError(std::io::Error),
|
||||
//
|
||||
// /// The file path provided for upload is invalid.
|
||||
// FileForUploadHasInvalidPath,
|
||||
//
|
||||
// /// Something is wrong with the JWT bearer token.
|
||||
// /// This error originates on our end as we try to parse the JWT.
|
||||
// LocalJwtClaimsParseError(String),
|
||||
//
|
||||
// /// There was an error constructing the form-multipart request.
|
||||
// MultipartFormError(wreq::Error),
|
||||
//
|
||||
// /// We haven't received a bearer token yet
|
||||
// /// This is our own internal application state error, not something Sora returns.
|
||||
// /// We know our client can't make the request, so we preemptively fail it.
|
||||
// NoBearerTokenForRequest,
|
||||
//
|
||||
// /// A sentinel token is not present in the client, which is required for some requests.
|
||||
// NoSentinelTokenForRequest,
|
||||
//
|
||||
// /// Issue with using the SoraCredentialBuilder.
|
||||
// SoraCredentialBuilderError(&'static str),
|
||||
//
|
||||
// /// Error parsing a request URL.
|
||||
// UrlParseError(url::ParseError),
|
||||
//
|
||||
// /// Error serializing the sentinel store token to JSON (typically for persistent storage).
|
||||
// CouldNotSerializeSentinelTokenStore(serde_json::Error),
|
||||
//
|
||||
// /// Error deserializing the sentinel token from JSON (typically for persistent storage).
|
||||
// CouldNotDeserializeSentinelTokenStore {
|
||||
// error: serde_json::Error,
|
||||
// raw_json: String
|
||||
// },
|
||||
}
|
||||
|
||||
impl Error for WorldLabsClientError {}
|
||||
|
||||
impl Display for WorldLabsClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::WreqClientError(err) => write!(f, "Wreq client error (during client creation): {}", err),
|
||||
Self::WebsocketRequestSerializationError(err) => write!(f, "Websocket request serialization error: {}", err),
|
||||
Self::WebsocketLockError => write!(f, "Websocket lock error"),
|
||||
Self::WebsocketReadError(err) => write!(f, "Websocket read error: {}", err),
|
||||
Self::WebsocketSendError(err) => write!(f, "Websocket send error: {}", err),
|
||||
Self::CannotOpenLocalFileForUpload(err) => write!(f, "Cannot open local file for upload: {}", err),
|
||||
Self::CannotReadLocalFileForUpload(err) => write!(f, "Cannot read local file for upload: {}", err),
|
||||
Self::CannotOpenLocalFileForWriting(err) => write!(f, "Cannot open local file for writing: {}", err),
|
||||
Self::CannotWriteLocalFile(err) => write!(f, "Cannot write local file: {}", err),
|
||||
Self::FileForUploadHasInvalidPath => write!(f, "File for upload has invalid path"),
|
||||
Self::HtmlParsingError => write!(f, "Html parsing error"),
|
||||
Self::FailedToDecodeVerificationToken(err) => write!(f, "Failed to decode verification token: {}", err),
|
||||
Self::InvalidVerificationTokenBytes => write!(f, "Invalid verification token bytes"),
|
||||
Self::ScriptLogicOutOfDate => write!(f, "Script logic out of date"),
|
||||
Self::ScriptSvgLogicOutOfDate => write!(f, "Script logic out of date (SVG parsing)"),
|
||||
Self::Script1LogicOutOfDate => write!(f, "Script logic out of date (script 1)"),
|
||||
Self::Script2LogicOutOfDate => write!(f, "Script logic out of date (script 2)"),
|
||||
Self::ScriptXsidLogicOutOfDate => write!(f, "Script logic out of date (xsid script)"),
|
||||
Self::BadSignatureInputs => write!(f, "Bad signature inputs"),
|
||||
Self::TimeoutMathBroken => write!(f, "Timeout math is broken"),
|
||||
Self::NoCookiesPresent => write!(f, "No cookies present"),
|
||||
Self::ErrorGeneratingVideo => write!(f, "Error generating video"),
|
||||
|
||||
//Self::FileForUploadReadError(err) => write!(f, "Error reading file for upload: {}", err),
|
||||
//Self::FileForUploadHasInvalidPath => write!(f, "The file path provided for upload is invalid."),
|
||||
//Self::LocalJwtClaimsParseError(msg) => write!(f, "Local JWT claims parse error: {}", msg),
|
||||
//Self::MultipartFormError(err) => write!(f, "Multipart form error: {}", err),
|
||||
//Self::NoBearerTokenForRequest => write!(f, "No bearer token available. The client needs a bearer token to make the request."),
|
||||
//Self::NoSentinelTokenForRequest => write!(f, "No sentinel token available. The client needs a sentinel token to make the request."),
|
||||
//Self::SoraCredentialBuilderError(msg) => write!(f, "Sora Credential Builder error: {}", msg),
|
||||
//Self::UrlParseError(err) => write!(f, "URL parse error: {}", err),
|
||||
//Self::CouldNotSerializeSentinelToken(err) => write!(f, "Could not serialize sentinel token to JSON: {}", err),
|
||||
//Self::CouldNotSerializeSentinelTokenStore(err) => write!(f, "Could not serialize sentinel token store to JSON: {}", err),
|
||||
//Self::CouldNotDeserializeSentinelTokenStore { error, raw_json} => {
|
||||
// write!(f, "Could not deserialize sentinel token store from JSON: {:?}, raw JSON: {}", error, raw_json)
|
||||
//},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use crate::error::world_labs_client_error::WorldLabsClientError;
|
||||
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
|
||||
use crate::error::world_labs_specific_api_error::WorldLabsSpecificApiError;
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorldLabsError {
|
||||
Client(WorldLabsClientError),
|
||||
ApiSpecific(WorldLabsSpecificApiError),
|
||||
ApiGeneric(WorldLabsGenericApiError),
|
||||
}
|
||||
|
||||
impl WorldLabsError {
|
||||
pub fn is_world_labs_having_downtime_issues(&self) -> bool {
|
||||
match self {
|
||||
Self::ApiGeneric(WorldLabsGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true,
|
||||
Self::ApiGeneric(WorldLabsGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true,
|
||||
Self::ApiGeneric(WorldLabsGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for WorldLabsError {}
|
||||
|
||||
impl std::fmt::Display for WorldLabsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Client(e) => write!(f, "WorldLabsClientError: {:?}", e),
|
||||
Self::ApiSpecific(e) => write!(f, "WorldLabsSpecificApiError: {:?}", e),
|
||||
Self::ApiGeneric(e) => write!(f, "WorldLabsGenericApiError: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorldLabsClientError> for WorldLabsError {
|
||||
fn from(error: WorldLabsClientError) -> Self {
|
||||
Self::Client(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorldLabsSpecificApiError> for WorldLabsError {
|
||||
fn from(error: WorldLabsSpecificApiError) -> Self {
|
||||
Self::ApiSpecific(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorldLabsGenericApiError> for WorldLabsError {
|
||||
fn from(error: WorldLabsGenericApiError) -> Self {
|
||||
Self::ApiGeneric(error)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use wreq::StatusCode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorldLabsGenericApiError {
|
||||
/// Specific Cloudflare errors.
|
||||
CloudflareError(CloudflareError),
|
||||
|
||||
/// The downloaded index.html did not include the expected data.
|
||||
/// This is necessary for handshakes between client and server.
|
||||
IndexHtmlDidNotIncludeExpectedData {
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// The downloaded javascript did not include the expected data.
|
||||
/// This is necessary for handshakes between client and server.
|
||||
ScriptBodyDidNotIncludeExpectedData {
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// Error creating the websocket due to 401 Unauthorized, likely an authentication error with Grok.
|
||||
/// Unfortunately we can't read the response body text to diagnose more because wreq has some API design issues.
|
||||
LikelyWebsocketAuthentication401,
|
||||
|
||||
/// We received a 403 on websocket upgrade, likely from Cloudflare.
|
||||
/// Unfortunately we can't read the response body text to diagnose more because wreq has some API design issues.
|
||||
LikelyWebsocketCloudflare403,
|
||||
|
||||
/// Unknown status code on websocket upgrade.
|
||||
UnexpectedWebsocketUpgradeStatusCode(StatusCode),
|
||||
|
||||
/// serde_json::Error, likely from JSON deserialization schema mismatch.
|
||||
/// Includes the original body.
|
||||
SerdeResponseParseErrorWithBody(serde_json::Error, String),
|
||||
|
||||
/// serde_json::Error, likely from JSON deserialization schema mismatch.
|
||||
/// Includes the original body.
|
||||
/// 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,
|
||||
body: String,
|
||||
},
|
||||
|
||||
/// Our previous upload failed
|
||||
UploadFailed,
|
||||
|
||||
/// An error upgrading the connection to a websocket.
|
||||
WreqWebsocketUpgradeError(wreq::Error),
|
||||
|
||||
/// An uncaught error from the API client.
|
||||
WreqError(wreq::Error),
|
||||
}
|
||||
|
||||
impl Error for WorldLabsGenericApiError {}
|
||||
|
||||
impl Display for WorldLabsGenericApiError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CloudflareError(err) => write!(f, "Cloudflare error: {}", err),
|
||||
Self::IndexHtmlDidNotIncludeExpectedData { message } => write!(f, "IndexHtmlDidNotIncludeExpectedData ( message: {} )", message),
|
||||
Self::ScriptBodyDidNotIncludeExpectedData { message } => write!(f, "ScriptBodyDidNotIncludeExpectedData ( message: {} )", message),
|
||||
Self::LikelyWebsocketAuthentication401 => write!(f, "Likely websocket authentication 401 error (probably Grok authentication issue with cookies)"),
|
||||
Self::LikelyWebsocketCloudflare403 => write!(f, "Likely Cloudflare 403 error on websocket upgrade"),
|
||||
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),
|
||||
Self::WreqError(err) => write!(f, "Wreq client error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WorldLabsSpecificApiError {
|
||||
/// 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.
|
||||
// FailedToGenerateBearerToken,
|
||||
//
|
||||
// /// The endpoint told us we used an invalid JWT.
|
||||
// InvalidJwt,
|
||||
//
|
||||
// /// Sora has responded with "sentinel block".
|
||||
// /// This usually means we need to refresh our sentinel token.
|
||||
// SentinelBlockError,
|
||||
//
|
||||
// /// Sora requires users to set up an account username before they can generate images.
|
||||
// /// This requires the user to set up that username.
|
||||
// SoraUsernameNotYetCreated,
|
||||
//
|
||||
// /// Sora has responded with "token expired".
|
||||
// /// This (probably?) means we need to refresh our JWT bearer token.
|
||||
// TokenExpiredError,
|
||||
//
|
||||
// /// We're sending too many tasks to Sora.
|
||||
// /// Sora is telling us to cool it.
|
||||
// TooManyConcurrentTasks,
|
||||
//
|
||||
// // TODO: Try to make concrete error types for these distinct cases.
|
||||
// /// Unauthorized, cookie and/or bearer token expired. We'll need to ask for a refreshed login.
|
||||
// ///
|
||||
// /// Example message, e.g. from the upload endpoint:
|
||||
// /// {
|
||||
// /// "error": {
|
||||
// /// "message": "Your authentication token has expired. Please try signing in again.",
|
||||
// /// "type": "invalid_request_error",
|
||||
// /// "param": null,
|
||||
// /// "code": "token_expired"
|
||||
// /// }
|
||||
// /// }
|
||||
// UnauthorizedCookieOrBearerExpired,
|
||||
//
|
||||
// /// The Sora sentinel token response is missing fields
|
||||
// SentinelResponseIsMissingFields {
|
||||
// missing_token: bool,
|
||||
// missing_turnstile: bool,
|
||||
// raw_response: String,
|
||||
// },
|
||||
}
|
||||
|
||||
impl Error for WorldLabsSpecificApiError {}
|
||||
|
||||
impl Display for WorldLabsSpecificApiError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
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."),
|
||||
//Self::SentinelBlockError => write!(f, "Request blocked by Sentinel Token Block. Please refresh the sentinel token."),
|
||||
//Self::SoraUsernameNotYetCreated => write!(f, "Sora username not yet created. Please create a username in the Sora app or website."),
|
||||
//Self::TokenExpiredError => write!(f, "JWT token has expired. Please refresh the token."),
|
||||
//Self::TooManyConcurrentTasks => write!(f, "Too many concurrent tasks. Please wait."),
|
||||
//Self::UnauthorizedCookieOrBearerExpired => write!(f, "Unauthorized: cookie and/or bearer token expired"),
|
||||
//Self::SentinelResponseIsMissingFields { missing_token: token_missing, missing_turnstile: turnstile_missing, raw_response } => {
|
||||
// write!(f, "Sentinel response is missing fields. token_missing: {}, turnstile_missing: {}. Raw response: {}", token_missing, turnstile_missing, raw_response)
|
||||
//},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) mod test_utils;
|
||||
mod client;
|
||||
mod credentials;
|
||||
mod requests;
|
||||
|
||||
pub mod client;
|
||||
pub mod credentials;
|
||||
pub mod error;
|
||||
pub mod requests;
|
||||
+26212
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,6 @@
|
||||
|
||||
/// Referer header value
|
||||
pub(crate) const REFERER_VALUE: &str = "https://marble.worldlabs.ai/";
|
||||
|
||||
/// Origin header value
|
||||
pub(crate) const ORIGIN_VALUE: &str = "https://marble.worldlabs.ai/";
|
||||
@@ -0,0 +1 @@
|
||||
pub (crate) mod common_header_values;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod objects;
|
||||
pub (crate) mod common;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod objects_create_initial;
|
||||
@@ -0,0 +1,76 @@
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
use crate::error::world_labs_client_error::WorldLabsClientError;
|
||||
use crate::error::world_labs_error::WorldLabsError;
|
||||
use crate::requests::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC};
|
||||
use http_headers::values::accept::ACCEPT_ALL;
|
||||
use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE;
|
||||
use http_headers::values::connection::CONNECTION_KEEP_ALIVE;
|
||||
use http_headers::values::content_type::CONTENT_TYPE_APPLICATION_JSON;
|
||||
use http_headers::values::pragma::PRAGMA_NO_CACHE;
|
||||
use http_headers::values::priority::PRIORITY_HIGHEST;
|
||||
use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE};
|
||||
use http_headers::values::te::TE_TRAILERS;
|
||||
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE};
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
pub struct ObjectsCreateInitialArgs<'a> {
|
||||
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
pub bearer_token: &'a WorldLabsBearerToken,
|
||||
|
||||
}
|
||||
|
||||
pub async fn objects_create_initial(args: ObjectsCreateInitialArgs<'_>) -> Result<(), WorldLabsError> {
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
.build()
|
||||
.map_err(|err| WorldLabsClientError::WreqClientError(err))?;
|
||||
|
||||
/*
|
||||
-H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0' \
|
||||
-H 'Accept: * / *' \
|
||||
-H 'Accept-Language: en-US,en;q=0.5' \
|
||||
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
|
||||
-H 'Referer: https://marble.worldlabs.ai/' \
|
||||
-H 'content-type: application/json' \
|
||||
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk1MTg5MTkxMTA3NjA1NDM0NGUxNWUyNTY0MjViYjQyNWVlYjNhNWMiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiVmljdG9yIFZvaWNlIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FDZzhvY0t3MGFhWEtZU3F5LVhra242OXlkSUpGR25KQTM3UzEzWHZqTlk0ZDl4Q3pVektVdz1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS93bHQtdHJhaW5pbmctZ3NjIiwiYXVkIjoid2x0LXRyYWluaW5nLWdzYyIsImF1dGhfdGltZSI6MTc2NTMyNTM3NCwidXNlcl9pZCI6InNWZ0FRRHlIM0JmWU9CZkY2ZXBXVGpMSHlnWDIiLCJzdWIiOiJzVmdBUUR5SDNCZllPQmZGNmVwV1RqTEh5Z1gyIiwiaWF0IjoxNzY1MzI1Mzc0LCJleHAiOjE3NjUzMjg5NzQsImVtYWlsIjoidmljdG9ydm9pY2U4OUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjExNjQ1OTMwMDE5ODYxOTg4ODgzNiJdLCJlbWFpbCI6WyJ2aWN0b3J2b2ljZTg5QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.U_mp4iAw14poJz11UFKg3QVO6TSfE8Gps6VqLhg4GLLumjilgnuyD8mnKFznRFX0R-OyBf8mugVk59zfy4BugeZ0cXRkm1FWHqR9BVJZ9BQoEobXw4RqHkUvtgSe2qnx8a4fLUVp5zxNPpXfEjLAdcG4YnNwdZ7-TxyCDvO3U_WHkG3aAJup1q5KOqURDeYoQ7m7THeobyM3pnmDO03GIMdYgLV3Lnpvk6FTprtsV8SxWr8ZJ-Cy1g_I9wtKcB4B5bIFLjfV4r8W_-uq_g5ddbri-Yy28Ah97jxFIFOVChbPSbQpS-TRshW6M2cM5Y2EhGudpgVQBM-Tekco9fGSRw' \
|
||||
-H 'Origin: https://marble.worldlabs.ai' \
|
||||
-H 'Sec-GPC: 1' \
|
||||
-H 'Connection: keep-alive' \
|
||||
-H 'Sec-Fetch-Dest: empty' \
|
||||
-H 'Sec-Fetch-Mode: cors' \
|
||||
-H 'Sec-Fetch-Site: cross-site' \
|
||||
-H 'Priority: u=0' \
|
||||
-H 'Pragma: no-cache' \
|
||||
-H 'Cache-Control: no-cache' \
|
||||
-H 'TE: trailers' \
|
||||
--data-raw '{"metadata":{"version":"0.0.1","createdAt":1765325556528,"updatedAt":1765325556528,"useAdvancedEditing":false,"draftMode":false,"nodes":{}},"mime_type":"application/run+json"}'
|
||||
*/
|
||||
|
||||
let mut request_builder = client.post(URL)
|
||||
.header(ACCEPT, ACCEPT_ALL)
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
||||
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
|
||||
.header(REFERER, REFERER_VALUE)
|
||||
.header(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON)
|
||||
.header(AUTHORIZATION, args.bearer_token.to_bearer_token_string())
|
||||
.header(ORIGIN, ORIGIN_VALUE)
|
||||
.header(SEC_GPC, "1")
|
||||
.header(CONNECTION, CONNECTION_KEEP_ALIVE)
|
||||
.header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY)
|
||||
.header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS)
|
||||
.header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE)
|
||||
.header(PRIORITY, PRIORITY_HIGHEST)
|
||||
.header(PRAGMA, PRAGMA_NO_CACHE)
|
||||
.header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE)
|
||||
.header(TE, TE_TRAILERS);
|
||||
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_test_bearer_token_raw() -> AnyhowResult<String> {
|
||||
let cookies = read_to_string("/home/bt/Artcraft/credentials/world_labs_bearer.txt")?;
|
||||
let cookies = cookies.trim().to_string();
|
||||
Ok(cookies)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_test_bearer_token() -> AnyhowResult<WorldLabsBearerToken> {
|
||||
let cookies = get_test_bearer_token_raw()?;
|
||||
Ok(WorldLabsBearerToken::new(cookies))
|
||||
}
|
||||
@@ -3,13 +3,14 @@ use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn get_test_cookies() -> AnyhowResult<String> {
|
||||
pub (crate) fn get_test_cookies() -> AnyhowResult<String> {
|
||||
let cookies = read_to_string("/home/bt/Artcraft/credentials/world_lab_cookies.txt")?;
|
||||
let cookies = cookies.trim().to_string();
|
||||
Ok(cookies)
|
||||
}
|
||||
|
||||
pub fn get_typed_test_cookies() -> AnyhowResult<WorldLabsCookies> {
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_typed_test_cookies() -> AnyhowResult<WorldLabsCookies> {
|
||||
let cookies = get_test_cookies()?;
|
||||
Ok(WorldLabsCookies::new(cookies))
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod get_test_cookies;
|
||||
pub mod get_test_cookies;
|
||||
mod get_test_bearer_token;
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "http_headers"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
# NB: public `http_headers` crate appears to be abandoned, so this name seems fine. Wish we had namespacing, though.
|
||||
name = "http_headers"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
errors.workspace = true
|
||||
|
||||
# External
|
||||
# (None yet)
|
||||
|
||||
[dev-dependencies]
|
||||
# (None yet)
|
||||
@@ -0,0 +1,23 @@
|
||||
//! http_headers
|
||||
//!
|
||||
//! The purpose of this library is to centralize the definition of common header names
|
||||
//! and header values that are not already represented by common libraries.
|
||||
//!
|
||||
|
||||
// Never allow these
|
||||
#![forbid(private_bounds)]
|
||||
#![forbid(private_interfaces)]
|
||||
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
|
||||
|
||||
// Okay to toggle
|
||||
#![forbid(unreachable_patterns)]
|
||||
#![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
// Always allow
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod names;
|
||||
pub mod values;
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
/// `Priority` header name
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Priority
|
||||
pub const PRIORITY: &str = "Priority";
|
||||
|
||||
/// `Sec-Fetch-Dest` header name
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Dest
|
||||
pub const SEC_FETCH_DEST : &str = "Sec-Fetch-Dest";
|
||||
|
||||
/// `Sec-Fetch-Mode` header name
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Mode
|
||||
pub const SEC_FETCH_MODE: &str = "Sec-Fetch-Mode";
|
||||
|
||||
/// `Sec-Fetch-Site` header name
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site
|
||||
pub const SEC_FETCH_SITE : &str = "Sec-Fetch-Site";
|
||||
|
||||
/// `Sec-GPC` header name
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-GPC
|
||||
pub const SEC_GPC : &str = "Sec-GPC";
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
pub const ACCEPT_ALL : &str = "*/*";
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
/// eg. -H 'Cache-Control: no-cache'
|
||||
pub const CACHE_CONTROL_NO_CACHE: &str = "no-cache";
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
pub const CONNECTION_KEEP_ALIVE : &str = "keep-alive";
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
pub const CONTENT_TYPE_APPLICATION_JSON: &str = "application/json";
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod accept;
|
||||
pub mod cache_control;
|
||||
pub mod connection;
|
||||
pub mod content_type;
|
||||
pub mod pragma;
|
||||
pub mod priority;
|
||||
pub mod sec;
|
||||
pub mod te;
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
/// eg. -H 'Pragma: no-cache'
|
||||
pub const PRAGMA_NO_CACHE: &str = "no-cache";
|
||||
@@ -0,0 +1,2 @@
|
||||
/// eg. -H 'Priority: u=0'
|
||||
pub const PRIORITY_HIGHEST : &str = "u=0";
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
/// eg. -H 'Sec-Fetch-Dest: empty'
|
||||
pub const SEC_FETCH_DEST_EMPTY : &str = "empty";
|
||||
|
||||
/// eg. -H 'Sec-Fetch-Mode: cors'
|
||||
pub const SEC_FETCH_MODE_CORS : &str = "cors";
|
||||
|
||||
/// eg. -H 'Sec-Fetch-Site: cross-site'
|
||||
pub const SEC_FETCH_SITE_CROSS_SITE : &str = "cross-site";
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
/// eg. -H 'TE: trailers'
|
||||
pub const TE_TRAILERS : &str = "trailers";
|
||||
Reference in new issue
Block a user