Compare commits

..
Author SHA1 Message Date
Brandon Thomas f587e19d25 remove gpu loading error 2025-09-27 18:27:33 -04:00
740 changed files with 6746 additions and 24043 deletions
-3
View File
@@ -45,9 +45,6 @@ crates/lib/sqlite_queries/target/
# Javascript build directories
node_modules/
# Frontend build outputs
frontend/apps/editor3d/bundle-report.html
# Vim noise
*.swo
*.swp
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "13d435d8f681b73e4080748dcef0579e246e41049af67e1bd79ffc06aa6ba974"
}
@@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload,\n on_complete_primary_media_file_token,\n on_complete_primary_media_file_class,\n on_complete_batch_token,\n on_complete_primary_media_file_cdn_url,\n on_complete_primary_media_file_thumbnail_url_template,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n completed_at as \"completed_at: DateTime<Utc>\"\n FROM tasks\n WHERE is_dismissed_by_user == 0\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "task_status",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "task_type",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "model_type",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "provider",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "provider_job_id",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "frontend_caller",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "frontend_subscriber_id",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "frontend_subscriber_payload",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "on_complete_primary_media_file_token",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "on_complete_primary_media_file_class",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "on_complete_batch_token",
"ordinal": 11,
"type_info": "Text"
},
{
"name": "on_complete_primary_media_file_cdn_url",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "on_complete_primary_media_file_thumbnail_url_template",
"ordinal": 13,
"type_info": "Text"
},
{
"name": "created_at: DateTime<Utc>",
"ordinal": 14,
"type_info": "Int64"
},
{
"name": "updated_at: DateTime<Utc>",
"ordinal": 15,
"type_info": "Int64"
},
{
"name": "completed_at: DateTime<Utc>",
"ordinal": 16,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
false,
true
]
},
"hash": "47f94c127abc20683b25c257aed3c2190b9bd29eb829d61392f08e10fd986767"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "50a2449b87abed7df3302a74ecd0e8ab87940077a247636680cb09b2fe488321"
}
Generated
+340 -663
View File
File diff suppressed because it is too large Load Diff
+6 -28
View File
@@ -6,17 +6,16 @@ resolver = "2"
members = [
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/artcraft/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"crates/api_clients/artcraft/storyteller_client",
"crates/api_clients/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
"crates/api_clients/google_sign_in",
"crates/api_clients/grok_client",
"crates/api_clients/midjourney_client",
"crates/api_clients/openai_sora_client",
"crates/api_clients/openart_client",
"crates/api_clients/runwayml_client",
"crates/api_clients/storyteller_client",
"crates/api_clients/thumbnail_generator",
# CLI tools for operations, etc.
@@ -44,7 +43,6 @@ members = [
"crates/lib/caching/memory_store",
"crates/lib/caching/redis_caching",
"crates/lib/cloudflare_errors",
"crates/lib/cloudflare_mitigation",
"crates/lib/concurrency",
"crates/lib/cookie_store",
"crates/lib/cookies",
@@ -86,7 +84,6 @@ members = [
"crates/lib/testing",
"crates/lib/tts_common",
"crates/lib/url_config",
"crates/lib/url_utils",
"crates/lib/user_input_common",
"crates/lib/web_scrapers",
#"crates/lib/api_clients/fakeyou_client",
@@ -184,11 +181,10 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# Internal libs
# Setting paths here ensures we don't need to do so in every crate's Cargo.toml
# It makes refactoring much easier.
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
artcraft_api_defs = { path = "crates/api_clients/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
cloudflare_mitigation = { path = "crates/lib/cloudflare_mitigation" }
component_traits = { path = "crates/service/plugins/component_traits" }
cookie_store = { path = "crates/lib/cookie_store" }
crockford = { path = "crates/lib/data/crockford" }
@@ -209,12 +205,11 @@ openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/artcraft/storyteller_client" }
storyteller_client = { path = "crates/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
testing = { path = "crates/lib/testing" }
tokens = { path = "crates/schema/public/tokens" }
url_utils = { path = "crates/lib/url_utils" }
web_base64 = { path = "crates/lib/data/web_base64" }
# External libs
@@ -235,22 +230,19 @@ base64 = "0.22.1"
bytes = "1.10.1"
chrono = { version = "0.4.41", features = ["serde"] }
cookie = "0.18.1"
env = "1.0.1"
env_logger = "0.11.8"
futures = { version = "0.3.31" }
hex-literal = "1.0.0"
indexmap = "2.9.0"
log = "0.4.27"
once_cell = "1.21.3"
rand = "0.9.1"
regex = "1.12.2"
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
regex = "1.11.1"
reqwest = { version = "0.12.22", features = ["json", "cookies"] }
reqwest-websocket = { version = "0.5.0" }
serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.34+deprecated"
sha2 = "0.11.0-rc.2"
sha3 = "0.10.8"
tauri = { version = "2.8.3", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
tauri-plugin-dialog = "2.3.3"
@@ -263,17 +255,3 @@ thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["macros"] }
toml = "0.8.22"
url = { version = "2.5.4", features = ["serde"] }
uuid = "1.18.1"
# NB: wreq gets past fingerprinting, but we have to deal with platform-specific build issues.
#
# Mac:
# brew install --formula cmake pkg-config llvm
# Windows:
# choco install cmake -y
# choco install strawberryperl -y
# choco install pkgconfiglite -y
# choco install llvm -y
# choco install nasm -y
wreq = { version = "6.0.0-rc.20", features = ["json", "multipart", "cookies", "ws"] }
wreq-util = "3.0.0-rc.5"
@@ -1,20 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
select
u.display_name,
count(*) as sessions,
a.session_duration_seconds / 60 / 60 as hours,
a.measurement_count,
u.email_address,
a.os_platform,
a.os_version,
u.created_at,
u.maybe_source,
a.last_active_at
from analytics_app_sessions as a
left outer join users as u
on a.user_token = u.token
group by a.user_token
order by hours desc;
@@ -9,14 +9,6 @@
-- tasks_v1.sqlite - initial version
-- tasks_v2.sqlite - added model_type (nullable)
-- tasks_v3.sqlite - added frontend_caller, comments
-- tasks_v4.sqlite - added is_dismissed_by_user
-- tasks_v5.sqlite - added five fields:
-- on_complete_batch_token,
-- on_complete_primary_media_file_token,
-- on_complete_primary_media_file_class,
-- on_complete_primary_media_file_cdn_url,
-- on_complete_primary_media_file_thumbnail_url_template,
--
CREATE TABLE tasks (
-- Task auto-incrementing primary key.
@@ -58,32 +50,6 @@ CREATE TABLE tasks (
-- will be re-emitted back to the frontend.
frontend_subscriber_payload TEXT,
-- Whether the user has dismissed the task from view.
is_dismissed_by_user INTEGER NOT NULL DEFAULT 0,
-- OPTIONAL.
-- When the generation completes, the batch token (if any).
-- This will point to a collection of multiple files.
on_complete_batch_token TEXT,
-- OPTIONAL.
-- When the generation completes, the media token of the "primary" media file.
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
on_complete_primary_media_file_token TEXT,
-- OPTIONAL.
-- When the generation completes, the file type of the primary media file.
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
on_complete_primary_media_file_class TEXT,
-- OPTIONAL.
-- The URL to the media file.
on_complete_primary_media_file_cdn_url TEXT,
-- OPTIONAL.
-- The URL template for the thumbnail of the primary media file.
on_complete_primary_media_file_thumbnail_url_template TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
completed_at INTEGER DEFAULT NULL
@@ -1,7 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct SimpleGenericJsonSuccess {
pub success: bool,
}
@@ -1,19 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
use tokens::tokens::media_files::MediaFileToken;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct DeleteMediaFileRequest {
pub set_delete: bool,
/// NB: this is only to disambiguate when a user is both a mod and an author.
pub as_mod: Option<bool>,
}
/// For the URL PathInfo
#[derive(Serialize, Deserialize, ToSchema)]
pub struct DeleteMediaFilePathInfo {
pub token: MediaFileToken,
}
@@ -1,12 +0,0 @@
use crate::common::responses::media_links::MediaLinks;
/// Get the thumbnail template for either images or videos.
pub fn media_links_to_thumbnail_template(links: &MediaLinks) -> Option<&str> {
// NB: We only populate video previews for video tasks.
if let Some(video) = links.maybe_video_previews.as_ref() {
return Some(&video.animated_thumbnail_template);
}
// Image case - use the image thumbnail template if it exists.
links.maybe_thumbnail_template.as_deref()
}
@@ -1 +0,0 @@
pub mod media_links_to_thumbnail_template;
@@ -1,37 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_delete_request::basic_json_delete_request;
use artcraft_api_defs::common::responses::simple_generic_json_success::SimpleGenericJsonSuccess;
use artcraft_api_defs::media_file::delete_media_file::DeleteMediaFileRequest;
use artcraft_api_defs::subscriptions::get_session_subscription::GetSessionSubscriptionResponse;
use enums::common::payments_namespace::PaymentsNamespace;
use log::debug;
use tokens::tokens::media_files::MediaFileToken;
pub async fn delete_media_file(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
media_file_token: &MediaFileToken,
) -> Result<SimpleGenericJsonSuccess, StorytellerError> {
let url_path = get_url_path(media_file_token);
debug!("Requesting {:?}", &url_path);
// NB: We probably don't need to expose these options from FakeYou anymore.
let request = DeleteMediaFileRequest {
set_delete: true,
as_mod: None,
};
Ok(basic_json_delete_request(
api_host,
&url_path,
maybe_creds,
request,
).await?)
}
fn get_url_path(token: &MediaFileToken) -> String {
format!("/v1/media_files/file/{}", token.as_str())
}
@@ -1,71 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::api_error::ApiError;
use crate::error::client_error::ClientError;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
use crate::utils::filter_bad_response::filter_bad_response;
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
use log::{debug, info};
use reqwest::Client;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
pub async fn basic_json_delete_request<Req: Serialize, Res: DeserializeOwned>(
api_host: &ApiHost,
route_path: &str,
maybe_creds: Option<&StorytellerCredentialSet>,
request: Req,
) -> Result<Res, StorytellerError> {
let url = get_route(api_host, route_path);
info!("DELETE {:?}", &url);
let client = Client::builder()
.gzip(true)
.build()
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
let mut request_builder = client.delete(url)
.header("User-Agent", USER_AGENT)
.header("Accept", APPLICATION_JSON)
.header("Content-Type", APPLICATION_JSON);
if let Some(creds) = maybe_creds {
if let Some(header) = &creds.maybe_as_cookie_header() {
request_builder = request_builder.header("Cookie", header);
}
}
let request_body = serde_json::to_string(&request)
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
debug!("Request body: {:?}", request_body);
let response = request_builder
.body(request_body)
.send()
.await
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
let response = filter_bad_response(response)
.await
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
let response_body = &response.text()
.await
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
debug!("Response body: {:?}", response_body);
let response = serde_json::from_str(&response_body)
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
Ok(response)
}
fn get_route(api_host: &ApiHost, route_path: &str) -> String {
let api_hostname = api_host.to_api_hostname_and_scheme();
format!("{}{}", api_hostname, route_path)
}
@@ -1,9 +0,0 @@
pub (crate) mod basic_json_delete_request;
pub (crate) mod basic_json_get_request;
pub (crate) mod basic_json_post_request;
pub (crate) mod basic_query_string_post_request;
pub (crate) mod filter_bad_response;
pub (crate) mod http_get_anonymous;
pub mod api_host;
pub mod constants;
pub mod status_codes;
@@ -3,5 +3,4 @@ pub mod job_details;
pub mod media_file_cover_image_details;
pub mod media_links;
pub mod simple_entity_stats;
pub mod simple_generic_json_success;
pub mod user_details_light;
@@ -6,5 +6,4 @@ pub mod jobs;
pub mod media_file;
pub mod prompts;
pub mod stripe_artcraft;
pub mod subscriptions;
pub mod utils;
pub mod subscriptions;
@@ -1,2 +1 @@
pub mod delete_media_file;
pub mod list_batch_generated_media_files;
-51
View File
@@ -1,51 +0,0 @@
[package]
name = "grok_client"
edition = "2024"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "grok_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
cloudflare_errors.workspace = true
cloudflare_mitigation.workspace = true
cookie_store.workspace = true
errors.workspace = true
# External
anyhow.workspace = true
base64.workspace = true
byteorder = "1.5.0"
chrono.workspace = true
futures.workspace = true
log.workspace = true
once_cell.workspace = true
regex.workspace = true
scraper = "0.24.0"
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
tokio = { version = "1", features = ["full"] }
url.workspace = true
uuid.workspace = true
wreq-util.workspace = true
wreq.workspace = true
[dev-dependencies]
filesys.workspace = true
test_data.workspace = true
testing.workspace = true
log4rs = "1.4.0"
env_logger.workspace = true
env.workspace = true
chrono.workspace = true
@@ -1,10 +0,0 @@
/// Firefox 139 (2025) on macOS, which matches wreq's Emulation::Firefox139
pub const FIREFOX_139_MAC_USER_AGENT : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0";
pub const FIREFOX_143_MAC_USER_AGENT : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0";
pub const FIREFOX_144_MAC_USER_AGENT_UNSUPPORTED_TLS_FINGERPRINTING : &str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0";
@@ -1 +0,0 @@
pub mod browser_user_agents;
@@ -1,517 +0,0 @@
#!/usr/bin/env python3
# From https://github.com/kbtit25/grok2api/blob/73c917b15438e12699531ccf9411d3a7d1110cbf/xStatsigIDGenerator.py
# -*- coding: utf-8 -*-
import base64
import struct
import hashlib
import time
import secrets
import requests
import re
import json
from typing import Optional, Dict, Any
class XStatsigIDGenerator:
"""x-statsig-id 生成器"""
def __init__(self):
self.base_timestamp = int(time.time()) # 使用当前系统时间
self.grok_url = "https://grok.com"
def get_grok_meta_content(self) -> bytes:
"""
从 grok.com 获取 meta 标签中的 grok-site-verification 内容
使用多种方法彻底解决403问题
Returns:
48字节的meta内容
"""
print("🌐 正在请求 grok.com...")
# 定义多种绕过策略
strategies = [
#self._try_curl_with_proxy,
self._try_curl_with_different_ua,
#self._try_requests_with_session,
#self._try_curl_cffi_advanced,
#self._try_alternative_endpoints,
#self._try_cached_content
]
for i, strategy in enumerate(strategies):
try:
print(f" 尝试策略 {i+1}: {strategy.__name__}")
result = strategy()
if result:
return result
except Exception as e:
print(f" 策略 {i+1} 失败: {e}")
continue
# 所有策略都失败,使用备用内容
print(" ❌ 所有策略都失败,使用备用meta内容")
fallback = b"backup-grok-meta-content-when-request-fails-ok"
return fallback + b'\x00' * (48 - len(fallback))
def _try_curl_with_proxy(self) -> bytes:
"""策略1: 使用curl + 代理"""
import subprocess
# 尝试多个公共代理
proxies = [
None, # 无代理
"socks5://127.0.0.1:1080", # 本地代理
"http://127.0.0.1:8080", # 本地HTTP代理
]
for proxy in proxies:
try:
curl_command = [
'curl', '-s', '-L', '--max-time', '15',
'--user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'--header', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'--header', 'Accept-Language: en-US,en;q=0.5',
'--header', 'Accept-Encoding: gzip, deflate',
'--header', 'Connection: keep-alive',
'--header', 'Upgrade-Insecure-Requests: 1',
'--compressed'
]
if proxy:
curl_command.extend(['--proxy', proxy])
curl_command.append(self.grok_url)
result = subprocess.run(curl_command, capture_output=True, text=True, timeout=15)
if result.returncode == 0 and len(result.stdout) > 1000:
print(f" ✅ curl成功 (代理: {proxy or '无'})")
return self._extract_meta_from_html(result.stdout)
except Exception:
continue
return None
def _try_curl_with_different_ua(self) -> bytes:
"""策略2: 使用不同的User-Agent"""
import subprocess
user_agents = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0',
#'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
#'Mozilla/5.0 (Android 13; Mobile; rv:109.0) Gecko/109.0 Firefox/119.0',
#'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
#'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15'
]
for ua in user_agents:
try:
curl_command = [
'curl-impersonate-ff', '-s', '-L', '--max-time', '10',
'--user-agent', ua,
'--header', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'--header', 'Accept-Language: en-US,en;q=0.9',
'--header', 'Cache-Control: no-cache',
'--header', 'Pragma: no-cache',
'--compressed',
self.grok_url
]
result = subprocess.run(curl_command, capture_output=True, text=True, timeout=10)
if result.returncode == 0 and len(result.stdout) > 1000:
print(f" ✅ 不同UA成功")
return self._extract_meta_from_html(result.stdout)
except Exception:
continue
return None
def _try_requests_with_session(self) -> bytes:
"""策略3: 使用requests session模拟真实浏览器行为"""
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
# 配置重试策略
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
# 模拟真实浏览器行为
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Cache-Control': 'max-age=0'
}
try:
# 先访问主页建立session
session.get('https://x.com', headers=headers, timeout=5)
# 再访问grok
response = session.get(self.grok_url, headers=headers, timeout=10)
if response.status_code == 200 and len(response.text) > 1000:
print(f" ✅ session请求成功")
return self._extract_meta_from_html(response.text)
except Exception:
pass
return None
def _try_curl_cffi_advanced(self) -> bytes:
"""策略4: 使用curl_cffi高级模拟"""
try:
from curl_cffi import requests as curl_requests
# 尝试不同的浏览器模拟
impersonations = ['chrome120', 'chrome119', 'safari17', 'firefox119']
for imp in impersonations:
try:
response = curl_requests.get(
self.grok_url,
impersonate=imp,
timeout=10,
headers={
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': 'no-cache'
}
)
if response.status_code == 200 and len(response.text) > 1000:
print(f" ✅ curl_cffi成功 ({imp})")
return self._extract_meta_from_html(response.text)
except Exception:
continue
except ImportError:
pass
return None
def _try_alternative_endpoints(self) -> bytes:
"""策略5: 尝试替代端点"""
import subprocess
# 尝试不同的URL路径
alternative_urls = [
'https://grok.com/',
'https://grok.com/login',
'https://grok.com/home',
'https://x.com/i/grok'
]
for url in alternative_urls:
try:
curl_command = [
'curl', '-s', '-L', '--max-time', '8',
'--user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'--header', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'--compressed',
url
]
result = subprocess.run(curl_command, capture_output=True, text=True, timeout=8)
if result.returncode == 0 and len(result.stdout) > 500:
print(f" ✅ 替代端点成功: {url}")
meta_result = self._extract_meta_from_html(result.stdout)
if meta_result:
return meta_result
except Exception:
continue
return None
def _try_cached_content(self) -> bytes:
"""策略6: 使用缓存或预设内容"""
# 如果有真实的grok meta内容,可以在这里硬编码
known_meta_contents = [
"grok-site-verification-content-2024-production-v1",
"x-grok-verification-meta-content-stable-release",
"grok-meta-verification-string-for-api-access"
]
for content in known_meta_contents:
try:
meta_bytes = content.encode('utf-8')
if len(meta_bytes) < 48:
meta_bytes = meta_bytes + b'\x00' * (48 - len(meta_bytes))
elif len(meta_bytes) > 48:
meta_bytes = meta_bytes[:48]
print(f" ✅ 使用预设内容: {content[:30]}...")
return meta_bytes
except Exception:
continue
return None
def _extract_meta_from_html(self, html_content: str) -> bytes:
"""从HTML中提取meta内容"""
patterns = [
r'<meta\s+name=["\']grok-site-verification["\']\s+content=["\']([^"\']+)["\']',
r'<meta\s+content=["\']([^"\']+)["\']\s+name=["\']grok-site-verification["\']',
r'grok-site-verification["\']?\s*(?:content|value)\s*=\s*["\']([^"\']+)["\']',
# 扩展模式,查找其他可能的meta标签
r'<meta\s+name=["\']verification["\']\s+content=["\']([^"\']+)["\']',
r'<meta\s+name=["\']site-verification["\']\s+content=["\']([^"\']+)["\']'
]
for pattern in patterns:
match = re.search(pattern, html_content, re.IGNORECASE)
if match:
verification_content = match.group(1)
print(f" ✅ 找到meta内容: {verification_content[:50]}...")
# 转换为48字节
meta_bytes = verification_content.encode('utf-8')
if len(meta_bytes) < 48:
meta_bytes = meta_bytes + b'\x00' * (48 - len(meta_bytes))
elif len(meta_bytes) > 48:
meta_bytes = meta_bytes[:48]
return meta_bytes
# 如果没找到特定的meta标签,尝试从HTML中提取其他有用信息
if 'grok' in html_content.lower() and len(html_content) > 1000:
# 使用HTML内容的哈希作为meta内容
import hashlib
content_hash = hashlib.sha256(html_content.encode()).hexdigest()[:48]
meta_bytes = content_hash.encode('utf-8')
if len(meta_bytes) < 48:
meta_bytes = meta_bytes + b'\x00' * (48 - len(meta_bytes))
print(f" ✅ 使用内容哈希作为meta: {content_hash[:32]}...")
return meta_bytes
return None
def generate_browser_fingerprint(self) -> str:
"""
生成浏览器指纹信息 (结合方法1和方法3)
Returns:
指纹字符串
"""
print("🔍 生成浏览器指纹...")
# 模拟浏览器指纹信息
fingerprint_data = {
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0",
"language": "en",
"languages": ["en", "en-US"],
"platform": "MacIntel",
"cookieEnabled": True,
"doNotTrack": None,
"screenWidth": 450,
"screenHeight": 654,
"screenColorDepth": 24,
"screenPixelDepth": 24,
"screenAvailWidth": 450,
"screenAvailHeight": 654,
"innerWidth": 450,
"innerHeight": 654,
"outerWidth": 1920,
"outerHeight": 1055,
"timezone": "America/New York",
"timezoneOffset": -400,
"hardwareConcurrency": 14,
"deviceMemory": 16,
"maxTouchPoints": 0
}
# 方法3: 生成指纹哈希
fingerprint_string = json.dumps(fingerprint_data, sort_keys=True, separators=(',', ':'))
fingerprint_hash = hashlib.sha256(fingerprint_string.encode('utf-8')).hexdigest()
print(f" 指纹数据长度: {len(fingerprint_string)} 字符")
print(f" 指纹哈希: {fingerprint_hash[:32]}...")
return fingerprint_hash
def generate_x_statsig_id(self, method: str = "GET", pathname: str = "/") -> str:
"""
生成完整的 x-statsig-id
Args:
method: 请求方式 (GET/POST)
pathname: 请求路径
Returns:
生成的 x-statsig-id 字符串
"""
print("=" * 60)
print("🚀 开始生成 x-statsig-id")
print("=" * 60)
print(f"📋 生成参数:")
print(f" Method: {method}")
print(f" Pathname: {pathname}")
# 1. 获取 grok.com 的 meta content
meta_content = self.get_grok_meta_content()
# 2. 生成浏览器指纹
fingerprint = self.generate_browser_fingerprint()
# 3. 生成当前时间戳
current_timestamp = int(time.time())
relative_timestamp = current_timestamp - self.base_timestamp
print(f"⏰ 时间信息:")
print(f" 当前时间戳: {current_timestamp}")
print(f" 基准时间戳: {self.base_timestamp}")
print(f" 相对时间戳: {relative_timestamp}")
# 4. 生成时间戳字节 (小端序)
timestamp_bytes = struct.pack('<I', relative_timestamp)
print(f" 时间戳字节: {timestamp_bytes.hex()}")
# 5. 生成SHA256
sha_input = f"{method}!{pathname}!{relative_timestamp}{fingerprint}"
sha256_hash = hashlib.sha256(sha_input.encode('utf-8')).digest()
sha256_16bytes = sha256_hash[:16]
print(f"🔐 SHA256信息:")
print(f" 输入字符串: {sha_input[:100]}...")
print(f" SHA256前16字节: {sha256_16bytes.hex()}")
# 6. 固定值
fixed_byte = b'\x03'
# 7. 组合payload数据
payload_data = meta_content + timestamp_bytes + sha256_16bytes + fixed_byte
print(f"📦 Payload长度: {len(payload_data)} 字节")
# 8. 生成异或key并加密
xor_key = secrets.randbits(8)
encrypted_payload = bytes([b ^ xor_key for b in payload_data])
print(f"🔑 异或信息:")
print(f" 异或key: 0x{xor_key:02x} ({xor_key})")
# 9. 组合最终数据
final_data = bytes([xor_key]) + encrypted_payload
print(f" 最终数据长度: {len(final_data)} 字节")
# 10. Base64编码
result = base64.b64encode(final_data).decode('utf-8')
print(f"✅ 生成结果:")
print(f" x-statsig-id: {result}")
print(f" 长度: {len(result)} 字符")
return result
def verify_generated_id(self, statsig_id: str) -> bool:
"""
验证生成的ID结构是否正确
Args:
statsig_id: 要验证的ID
Returns:
验证是否通过
"""
print("\n" + "=" * 60)
print("🔍 验证生成的ID结构")
print("=" * 60)
try:
# Base64解码
decoded_bytes = base64.b64decode(statsig_id)
print(f"✅ Base64解码成功,长度: {len(decoded_bytes)} 字节")
# 提取异或key
xor_key = decoded_bytes[0]
print(f"✅ 异或key: 0x{xor_key:02x} ({xor_key})")
# 异或解密
decrypted = bytearray()
for i in range(1, len(decoded_bytes)):
decrypted.append(decoded_bytes[i] ^ xor_key)
print(f"✅ 解密后长度: {len(decrypted)} 字节")
# 验证数据结构
expected_length = 48 + 4 + 16 + 1 # meta + timestamp + sha256 + fixed
if len(decrypted) == expected_length:
print(f"✅ 数据长度正确: {len(decrypted)}/{expected_length}")
else:
print(f"❌ 数据长度错误: {len(decrypted)}/{expected_length}")
return False
# 检查固定值
fixed_val = decrypted[-1]
if fixed_val == 3:
print(f"✅ 固定值正确: {fixed_val}")
else:
print(f"❌ 固定值错误: {fixed_val} (期望: 3)")
return False
# 解析时间戳
timestamp_bytes = decrypted[48:52]
timestamp = struct.unpack('<I', timestamp_bytes)[0]
actual_time = self.base_timestamp + timestamp
print(f"✅ 时间戳解析:")
print(f" 相对时间: {timestamp} 秒")
print(f" 绝对时间: {actual_time}")
print(f" 时间差: {abs(time.time() - actual_time):.1f} 秒")
print("🎉 ID结构验证通过!")
return True
except Exception as e:
print(f"❌ 验证失败: {e}")
return False
def main():
"""主函数 - 演示完整流程"""
generator = XStatsigIDGenerator()
# 生成 x-statsig-id
method = "POST"
pathname = "/rest/app-chat/conversations/new"
statsig_id = generator.generate_x_statsig_id(method, pathname)
print("Generated id", statsig_id)
# 验证生成的ID
generator.verify_generated_id(statsig_id)
print(f"\n" + "=" * 60)
print("📋 使用说明")
print("=" * 60)
print("在HTTP请求中使用:")
print(f"Headers: {{'x-statsig-id': '{statsig_id}'}}")
if __name__ == "__main__":
main()
@@ -1,193 +0,0 @@
#!/usr/bin/env python3
# From https://github.com/realasfngl/Grok-Api/blob/d8f7c6754d95ad3707b214f93930ee454724035d/core/xctid.py
from math import floor, copysign, pi, cos, sin
from base64 import b64decode, b64encode
from re import findall, sub
from typing import List, Dict
from random import random
from hashlib import sha256
from struct import pack
from time import time
class Signature:
@staticmethod
def _h(x: float, _param: float, c: float, e: bool):
f = ((x * (c - _param)) / 255.0) + _param
if e:
return floor(f)
rounded = round(float(f), 2)
if rounded == 0.0:
return 0.0
return rounded
@staticmethod
def cubicBezierEased(t: float, x1: float, y1: float, x2: float, y2: float) -> float:
def bezier(u: float):
omu = 1.0 - u
b1 = 3.0 * omu * omu * u
b2 = 3.0 * omu * u * u
b3 = u * u * u
x = b1 * x1 + b2 * x2 + b3
y = b1 * y1 + b2 * y2 + b3
return x, y
lo, hi = 0.0, 1.0
for _ in range(80):
mid = 0.5 * (lo + hi)
if bezier(mid)[0] < t:
lo = mid
else:
hi = mid
u = 0.5 * (lo + hi)
return bezier(u)[1]
@staticmethod
def xa(svg: str) -> List[List[int]]:
s = (svg)
substr = s[9:]
parts = substr.split("C")
out = []
for part in parts:
cleaned = sub(r"[^\d]+", " ", part).strip()
if cleaned == "":
nums = [0]
else:
nums = [int(tok) for tok in cleaned.split() if tok != ""]
out.append(nums)
return out
@staticmethod
def tohex(num: float) -> str:
rounded = round(float(num), 2)
if rounded == 0.0:
return "0"
sign = "-" if copysign(1.0, rounded) < 0 else ""
absval = abs(rounded)
intpart = int(floor(absval))
frac = absval - intpart
if frac == 0.0:
return sign + format(intpart, "x")
frac_digits = []
f = frac
for _ in range(20):
f *= 16
digit = int(floor(f + 1e-12))
frac_digits.append(format(digit, "x"))
f -= digit
if abs(f) < 1e-12:
break
frac_str = "".join(frac_digits).rstrip("0")
if frac_str == "":
return sign + format(intpart, "x")
return sign + format(intpart, "x") + "." + frac_str
@staticmethod
def simulateStyle(values: List[int], c: int) -> Dict[str,str]:
duration = 4096
currentTime = round(c / 10.0) * 10
t = currentTime / duration
cp = [Signature._h(v, -1 if (i % 2) else 0, 1, False) for i, v in enumerate(values[7:])]
easedY = Signature.cubicBezierEased(t, cp[0], cp[1], cp[2], cp[3])
start = [float(x) for x in values[0:3]]
end = [float(x) for x in values[3:6]]
r = round(start[0] + (end[0] - start[0]) * easedY)
g = round(start[1] + (end[1] - start[1]) * easedY)
b = round(start[2] + (end[2] - start[2]) * easedY)
color = f"rgb({r}, {g}, {b})"
endAngle = Signature._h(values[6], 60, 360, True)
angle = endAngle * easedY
rad = angle * pi / 180.0
def is_effectively_zero(val: float) -> bool:
return abs(val) < 1e-7
def is_effectively_integer(val: float) -> bool:
return abs(val - round(val)) < 1e-7
cosv = cos(rad)
sinv = sin(rad)
if is_effectively_zero(cosv):
a = 0
d = 0
else:
if is_effectively_integer(cosv):
a = int(round(cosv))
d = int(round(cosv))
else:
a = f"{cosv:.6f}"
d = f"{cosv:.6f}"
if is_effectively_zero(sinv):
bval = 0
cval = 0
else:
if is_effectively_integer(sinv):
bval = int(round(sinv))
cval = int(round(-sinv))
else:
bval = f"{sinv:.7f}"
cval = f"{(-sinv):.7f}"
transform = f"matrix({a}, {bval}, {cval}, {d}, 0, 0)"
return {"color": color, "transform": transform}
@staticmethod
def xs(x_bytes: bytes, svg: str, x_values: list) -> str:
arr = list(x_bytes)
idx = arr[x_values[0]] % 16
c = ((arr[x_values[1]] % 16) * (arr[x_values[2]] % 16)) * (arr[x_values[3]] % 16)
o = Signature.xa(svg)
vals = o[idx]
k = Signature.simulateStyle(vals, c)
concat = str(k["color"]) + str(k["transform"])
matches = findall(r"[\d\.\-]+", concat)
converted = []
for m in matches:
num = float(m)
hexstr = Signature.tohex(num)
converted.append(hexstr)
joined = "".join(converted)
cleaned = joined.replace(".", "").replace("-", "")
return cleaned
@staticmethod
def generate_sign(path: str, method: str, verification: str, svg: str, x_values: list, time_n: int = None, random_float: float = None) -> str:
n = int(time() - 1682924400) if not time_n else time_n
t = pack('<I', n)
r = b64decode(verification)
o = Signature.xs(r, svg, x_values)
msg = "!".join([method, path, str(n)]) + "obfiowerehiring" + o
digest = sha256(msg.encode('utf-8')).digest()[:16]
prefix_byte = int(floor(random() if not random_float else random_float * 256))
assembled = bytes([prefix_byte]) + r + t + digest + bytes([3])
arr = bytearray(assembled)
if len(arr) > 0:
first = arr[0]
for i in range(1, len(arr)):
arr[i] = arr[i] ^ first
return b64encode(bytes(arr)).decode('ascii').replace('=', '')
def main():
sig = Signature.generate_sign('/rest/app-chat/conversations/new', 'POST', self.verification_token, self.svg_data, self.numbers)
print('Signature', sig)
pass
if __name__ == 'main':
main()
@@ -1,8 +0,0 @@
https://grok.com/ --> https://accounts.x.ai/sign-in?redirect=grok-com
--> Success --> https://grok.com/
https://grok.com/imagine
@@ -1,36 +0,0 @@
use crate::datatypes::api::baggage::Baggage;
use crate::datatypes::api::sentry_trace::SentryTrace;
use crate::datatypes::api::svg_path_data::SvgPathData;
use crate::datatypes::api::user_email::UserEmail;
use crate::datatypes::api::user_id::UserId;
use crate::datatypes::api::verification_token::VerificationToken;
use crate::datatypes::api::xsid_numbers::XsidNumbers;
#[derive(Clone)]
pub struct GrokClientSecrets {
/// Baggage is <meta> tag data from the index.html
pub baggage: Baggage,
/// Baggage is <meta> tag data from the index.html
pub sentry_trace: SentryTrace,
/// Baggage is <meta> tag data from the index.html
pub verification_token: VerificationToken,
/// The SVG path data to use (chosen via verification_token)
/// We selected one of four-ish possible SVG paths (of length >= 200) by
/// the `verification token -> animation index` algo.
pub svg_path_data: SvgPathData,
/// This doesn't come from the index.html
/// These numbers are separately loaded from the xsid javascript
pub numbers: XsidNumbers,
/// From index.html
/// Not strictly needed to sign requests, but typically needed to generate URLs.
pub user_id: UserId,
/// From index.html
/// Not needed, but returned alongside other details.
pub user_email: Option<UserEmail>,
}
@@ -1,24 +0,0 @@
#[derive(Clone)]
pub struct GrokCookies {
/// The entire cookies header (for now)
cookies: String,
}
impl GrokCookies {
pub fn new(cookies: String) -> Self {
Self { cookies }
}
pub fn as_str(&self) -> &str {
&self.cookies
}
pub fn as_bytes(&self) -> &[u8] {
self.cookies.as_bytes()
}
pub fn to_string(&self) -> String {
self.cookies.clone()
}
}
@@ -1,25 +0,0 @@
use crate::credentials::grok_client_secrets::GrokClientSecrets;
use crate::credentials::grok_cookies::GrokCookies;
/// Includes the cookies and all the magic other bits needed to call the API.
#[derive(Clone)]
pub struct GrokFullCredentials {
/// Entire cookie payload
pub cookies: GrokCookies,
/// Secrets from `index.html` and the xsid javascript.
/// Required for video generation.
pub client_secrets: GrokClientSecrets,
}
impl GrokFullCredentials {
pub fn from_cookies_and_client_secrets(
cookies: GrokCookies,
client_secrets: GrokClientSecrets
) -> Self {
Self {
cookies,
client_secrets,
}
}
}
@@ -1,3 +0,0 @@
pub mod grok_cookies;
pub mod grok_full_credentials;
pub mod grok_client_secrets;
@@ -1,4 +0,0 @@
/// "baggage" data from the <meta> tags
#[derive(Debug, Clone)]
pub struct Baggage(pub(crate) String);
@@ -1,5 +0,0 @@
/// Type for File IDs.
/// File IDs are UUIds.
#[derive(Clone, Debug)]
pub struct FileId(pub String);
@@ -1,9 +0,0 @@
pub mod baggage;
pub mod file_id;
pub mod request_id;
pub mod sentry_trace;
pub mod svg_path_data;
pub mod user_email;
pub mod user_id;
pub mod verification_token;
pub mod xsid_numbers;
@@ -1,7 +0,0 @@
/// Request IDs.
/// Type for image websocket "tasks"
/// These are for image generation websockets.
/// Request IDs are UUIds.
#[derive(Clone, Debug)]
pub struct RequestId(pub String);
@@ -1,40 +0,0 @@
use uuid::Uuid;
/// "sentry trace" data from the <meta> tags
#[derive(Debug, Clone)]
pub struct SentryTrace(pub (crate) String);
impl SentryTrace {
/// Generate the format of trace sent in the HTTP request headers
pub fn to_http_request_header(&self) -> String {
// Python reference:
// f'{self.sentry_trace}-{str(uuid4()).replace("-", "")[:16]}-0',
// Inner bit:
// str(uuid4()).replace("-", "")[:16]
let sentry_start = &self.0;
let sentry_uuid = Uuid::new_v4().to_string();
let sentry_inner = sentry_uuid.replace("-", "")[..16].to_string();
format!("{sentry_start}-{sentry_inner}-0")
}
}
#[cfg(test)]
mod tests {
use crate::datatypes::api::sentry_trace::SentryTrace;
#[test]
fn test_real_trace() {
let short = SentryTrace("d6d7c55e4a489c0dabaed06e5c7b257b".to_string());
let header = short.to_http_request_header();
assert_eq!(header.len(), 51);
}
#[test]
fn test_invalid_trace() {
let short = SentryTrace("".to_string());
let header = short.to_http_request_header();
assert_eq!(header.len(), 19); // header is invalid, we're just making sure Rust doesn't panic
}
}
@@ -1,4 +0,0 @@
/// Data on an SVG path, eg. <svg d={data} />
#[derive(Debug, Clone)]
pub struct SvgPathData(pub(crate) String);
@@ -1,3 +0,0 @@
/// Type for User Emails
#[derive(Clone, Debug)]
pub struct UserEmail(pub String);
@@ -1,5 +0,0 @@
/// Type for User IDs.
/// User IDs are UUIDs.
#[derive(Clone, Debug)]
pub struct UserId(pub String);
@@ -1,3 +0,0 @@
#[derive(Debug, Clone)]
pub struct VerificationToken(pub(crate) String);
@@ -1,6 +0,0 @@
/// Magic numbers pulled from Javascript
#[derive(Clone, Debug)]
pub struct XsidNumbers {
pub numbers: Vec<u32>,
}
@@ -1,18 +0,0 @@
use std::path::Path;
/// Descriptor of a file to be uploaded
/// Includes necessary metadata
pub enum FileUploadSpec<P: AsRef<Path>> {
/// Path to the file
Path(P),
/// Bytes of the file
Bytes {
/// Must be owned.
bytes: Vec<u8>,
/// Must be owned.
filename: String,
/// Mime type
mimetype: String,
}
}
@@ -1,2 +0,0 @@
pub mod api;
pub mod file_upload_spec;
@@ -1,122 +0,0 @@
use base64::DecodeError;
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum GrokClientError {
/// 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),
/// 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,
/// Signature algorithm isn't working with inputs
BadSignatureInputs,
/// Something is broken with timeout math
TimeoutMathBroken,
// /// 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 GrokClientError {}
impl Display for GrokClientError {
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::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::BadSignatureInputs => write!(f, "Bad signature inputs"),
Self::TimeoutMathBroken => write!(f, "Timeout math is broken"),
//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)
//},
}
}
}
@@ -1,53 +0,0 @@
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
use crate::error::grok_specific_api_error::GrokSpecificApiError;
use cloudflare_errors::cloudflare_error::CloudflareError;
use std::error::Error;
#[derive(Debug)]
pub enum GrokError {
Client(GrokClientError),
ApiSpecific(GrokSpecificApiError),
ApiGeneric(GrokGenericApiError),
}
impl GrokError {
pub fn is_grok_having_downtime_issues(&self) -> bool {
match self {
Self::ApiGeneric(GrokGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true,
Self::ApiGeneric(GrokGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true,
Self::ApiGeneric(GrokGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true,
_ => false,
}
}
}
impl Error for GrokError {}
impl std::fmt::Display for GrokError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Client(e) => write!(f, "GrokClientError: {:?}", e),
Self::ApiSpecific(e) => write!(f, "GrokSpecificApiError: {:?}", e),
Self::ApiGeneric(e) => write!(f, "GrokGenericApiError: {:?}", e),
}
}
}
impl From<GrokClientError> for GrokError {
fn from(error: GrokClientError) -> Self {
Self::Client(error)
}
}
impl From<GrokSpecificApiError> for GrokError {
fn from(error: GrokSpecificApiError) -> Self {
Self::ApiSpecific(error)
}
}
impl From<GrokGenericApiError> for GrokError {
fn from(error: GrokGenericApiError) -> Self {
Self::ApiGeneric(error)
}
}
@@ -1,78 +0,0 @@
use cloudflare_errors::cloudflare_error::CloudflareError;
use std::error::Error;
use std::fmt::{Display, Formatter};
use wreq::StatusCode;
#[derive(Debug)]
pub enum GrokGenericApiError {
/// 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.
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 GrokGenericApiError {}
impl Display for GrokGenericApiError {
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::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),
}
}
}
@@ -1,72 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum GrokSpecificApiError {
TodoError,
// /// 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 GrokSpecificApiError {}
impl Display for GrokSpecificApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::TodoError => write!(f, "TodoError"),
//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,4 +0,0 @@
pub mod grok_client_error;
pub mod grok_error;
pub mod grok_generic_api_error;
pub mod grok_specific_api_error;

Some files were not shown because too many files have changed in this diff Show More