mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
openart api
This commit is contained in:
1 parent
6a01e4bba0
commit
855463f9d8
14 files changed
+327
No files matched your search
Generated
+30
@@ -6834,6 +6834,36 @@ dependencies = [
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openart_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"cookie 0.18.1",
|
||||
"crockford",
|
||||
"errors",
|
||||
"filesys",
|
||||
"hex-literal",
|
||||
"idempotency",
|
||||
"jwt_light",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rand 0.9.1",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"subprocess_common",
|
||||
"testing",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"toml 0.8.22",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.71"
|
||||
|
||||
@@ -27,6 +27,7 @@ members = [
|
||||
"crates/lib/api_clients/google_drive_common",
|
||||
"crates/lib/api_clients/google_sign_in",
|
||||
"crates/lib/api_clients/openai_sora_client",
|
||||
"crates/lib/api_clients/openart_client",
|
||||
"crates/lib/api_clients/runwayml_client",
|
||||
"crates/lib/api_clients/storyteller_client",
|
||||
"crates/lib/api_clients/thumbnail_generator",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "openart_client"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "openart_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
crockford = { path = "../../data/crockford" }
|
||||
errors = { path = "../../errors" }
|
||||
idempotency = { path = "../../idempotency" }
|
||||
jwt_light = { workspace = true }
|
||||
subprocess_common = { path = "../../subprocess_common" }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true}
|
||||
cookie = { workspace = true }
|
||||
hex-literal = { workspace = true }
|
||||
log = { workspace = true }
|
||||
once_cell= { workspace = true }
|
||||
rand = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["multipart", "gzip"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha3 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
toml = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../testing" }
|
||||
filesys = { path = "../../files/filesys" }
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod openart_cookies;
|
||||
pub mod openart_credentials;
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
/// The cookies payload is the entire cookie header (potentially many cookies).
|
||||
#[derive(Clone)]
|
||||
pub struct OpenArtCookies {
|
||||
cookies: String,
|
||||
}
|
||||
|
||||
impl OpenArtCookies {
|
||||
pub fn new(cookies: String) -> Self {
|
||||
OpenArtCookies { 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::creds::jwt_bearer_token::JwtBearerToken;
|
||||
use crate::creds::openart_cookies::OpenArtCookies;
|
||||
use crate::error::api_error::ApiError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenArtCredentials {
|
||||
pub cookies: Option<OpenArtCookies>,
|
||||
}
|
||||
|
||||
impl OpenArtCredentials {
|
||||
pub fn from_cookies(cookies: &str) -> Self {
|
||||
let cookies = OpenArtCookies::new(cookies.to_string());
|
||||
Self {
|
||||
cookies: Some(cookies),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use jwt_light::error::JwtError;
|
||||
use reqwest::StatusCode;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
/// An error that occurred as or after the request was sent.
|
||||
ReqwestError(reqwest::Error),
|
||||
|
||||
/// Some other failure response.
|
||||
UncategorizedBadResponse{
|
||||
status_code: StatusCode,
|
||||
message : String,
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApiError::JwtError(err) => write!(f, "JwtError: {}", err),
|
||||
ApiError::ReqwestError(err) => write!(f, "ReqwestError: {}", err),
|
||||
ApiError::UncategorizedBadResponse { status_code, message } => {
|
||||
write!(f, "UncategorizedBadResponse: {:?} : {}", status_code, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ApiError {}
|
||||
|
||||
impl From<JwtError> for ApiError {
|
||||
fn from(err: JwtError) -> Self {
|
||||
ApiError::JwtError(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
|
||||
/// This assumes the request failed and returned a non-200.
|
||||
/// The caller should check.
|
||||
pub async fn classify_http_error_response(response: reqwest::Response) -> OpenArtError {
|
||||
let status = response.status();
|
||||
let message = match response.text().await {
|
||||
Ok(text) => text,
|
||||
Err(err) => return OpenArtError::Api(ApiError::ReqwestError(err)),
|
||||
};
|
||||
|
||||
// TODO: Handle various error messages from the API.
|
||||
|
||||
OpenArtError::Api(ApiError::UncategorizedBadResponse { status_code: status, message })
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientError {
|
||||
/// No cookies in credentials
|
||||
NoCookiesInCredentials,
|
||||
|
||||
/// No JWT bearer token in the credentials
|
||||
NoJwtBearerToken,
|
||||
|
||||
/// An error that occurred clientside before the request was sent.
|
||||
ReqwestError(reqwest::Error),
|
||||
}
|
||||
|
||||
impl Display for ClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ClientError::NoCookiesInCredentials => write!(f, "No cookies are present in the credentials"),
|
||||
ClientError::NoJwtBearerToken => write!(f, "No JWT bearer token in the credentials"),
|
||||
ClientError::ReqwestError(err) => write!(f, "Reqwest error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ClientError {}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod api_error;
|
||||
pub mod classify_http_error_response;
|
||||
pub mod client_error;
|
||||
pub mod openart_error;
|
||||
@@ -0,0 +1,33 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpenArtError {
|
||||
Api(ApiError),
|
||||
Client(ClientError),
|
||||
}
|
||||
|
||||
impl Display for OpenArtError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Api(err) => write!(f, "ApiError: {}", err),
|
||||
Self::Client(err) => write!(f, "ClientError: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for OpenArtError {}
|
||||
|
||||
impl From<ApiError> for OpenArtError {
|
||||
fn from(err: ApiError) -> Self {
|
||||
OpenArtError::Api(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientError> for OpenArtError {
|
||||
fn from(err: ClientError) -> Self {
|
||||
OpenArtError::Client(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod requests;
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
curl 'https://openart.ai/api/auth/session' --compressed
|
||||
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0'
|
||||
-H 'Accept: * / *'
|
||||
-H 'Accept-Language: en-US,en;q=0.5'
|
||||
-H 'Accept-Encoding: gzip, deflate, br, zstd'
|
||||
-H 'Referer: https://openart.ai/create?mode=edit&imageAction=changeBackground&action_mode=background_manual_remove'
|
||||
-H 'Content-Type: application/json'
|
||||
-H 'Connection: keep-alive'
|
||||
-H 'Cookie: AMP_3e2fda7a5c=JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjJmZmRkZTQ2MS1jMmVlLTRmNmUtYmQwMi0zNDcwYjY1OWNkMTklMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjI2YWJ1ejg1Y1JvMWlWQW1rNlJqZiUyMiUyQyUyMnNlc3Npb25JZCUyMiUzQTE3NDg1ODQyODY0NzclMkMlMjJvcHRPdXQlMjIlM0FmYWxzZSUyQyUyMmxhc3RFdmVudFRpbWUlMjIlM0ExNzQ4NTg0NTU5NzEyJTJDJTIybGFzdEV2ZW50SWQlMjIlM0EzNjglMkMlMjJwYWdlQ291bnRlciUyMiUzQTklN0Q=; AMP_MKTG_3e2fda7a5c=JTdCJTIycmVmZXJyZXIlMjIlM0ElMjJodHRwcyUzQSUyRiUyRnd3dy5nb29nbGUuY29tJTJGJTIyJTJDJTIycmVmZXJyaW5nX2RvbWFpbiUyMiUzQSUyMnd3dy5nb29nbGUuY29tJTIyJTdE; __client_uat=0; __client_uat_Xt7g_-Hi=0; utm_params={%22utm_source%22:%22organic%22%2C%22utm_medium%22:%22Google%22%2C%22utm_campaign%22:%22oa_unknown%22%2C%22utm_term%22:%22oa_unknown%22%2C%22utm_content%22:%22oa_unknown%22}; themeMode=dark; themeDirection=ltr; themeColorPresets=default; themeLayout=horizontal; themeContrast=default; themeStretch=false; unique_device_id=f1f0b393-815f-4f95-aaa2-65a3b94530f6; __Host-next-auth.csrf-token=ea06d49af6d70cbe0d15774565708d6c394790c5ab2610276bb0bf29a4afb47b%7Cbdbf15d7f2bf44c83586c7ab1ec47ea6c114b26ccc8e20ae45f40f06c0377389; __Secure-next-auth.callback-url=https%3A%2F%2Fopenart.ai%2Fcreate; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mzdySrH34fIj41NP.ky2ZVPIugA1EWSKL29EEEKvSxfG4LCo-R7rN-yLzLo-2LmCrFzVe15BCH2MYg90cwkdIdm1Hi-7U4BcnxG0x662UrU9RDw2yX_ZTZge6Kz70-pg1TaVvKWOS_Gibv8ERSK6MHTfqlx4WNvHccOOfDIWhN87zbLXHCbWnexgmBOB3XfMA96Hby55JNgDM3-_JPcg1lFNkT8oAW562FUZxM9EMzKD4-A4Ee1ZpVd0Z51k4lqfS4XED-0xT6xagsCd6CdwEHEHc0paIAj34Kb_lCO2nyrxGBYvx1XlVHjvXdtakrTqe6jrOuV5rQ0iEO6Xk6cibcZydMV4GssKPEwUDS718AJCxNUK9yHi4RRyfYus.bhvOrrfLdszMcHfJ3Ex2wA'
|
||||
-H 'Sec-Fetch-Dest: empty'
|
||||
-H 'Sec-Fetch-Mode: cors'
|
||||
-H 'Sec-Fetch-Site: same-origin'
|
||||
-H 'Priority: u=4'
|
||||
-H 'Pragma: no-cache'
|
||||
-H 'Cache-Control: no-cache'
|
||||
-H 'TE: trailers'
|
||||
*/
|
||||
use log::{error, info};
|
||||
use crate::creds::openart_credentials::OpenArtCredentials;
|
||||
use crate::error::openart_error::OpenArtError;
|
||||
use reqwest::Client;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
|
||||
const SESSION_URL : &str = "https://openart.ai/api/auth/session";
|
||||
|
||||
pub async fn get_session_request(creds: &OpenArtCredentials) -> Result<String, OpenArtError> {
|
||||
|
||||
let cookies = match creds.cookies.as_ref() {
|
||||
Some(cookies) => cookies,
|
||||
None => {
|
||||
error!("Failed to request session. No cookies in credentials.");
|
||||
return Err(ClientError::NoCookiesInCredentials.into());
|
||||
}
|
||||
};
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
error!("Failed to create HTTP client: {}", err);
|
||||
OpenArtError::Client(ClientError::ReqwestError(err))
|
||||
})?;
|
||||
|
||||
info!("Getting session info from cookies... (cookie payload length: {})", cookies.as_str().len());
|
||||
|
||||
let mut http_request= client.get(SESSION_URL)
|
||||
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0")
|
||||
.header("Accept", "*/*")
|
||||
.header("Accept-Encoding", "gzip, deflate, br")
|
||||
.header("Accept-Language", "en-US,en;q=0.5")
|
||||
.header("Cookie", cookies.as_str());
|
||||
|
||||
let http_request = http_request.build()
|
||||
.map_err(|err| ApiError::ReqwestError(err))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| ApiError::ReqwestError(err))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error reading body while attempting to generate bearer token: {:?}", err);
|
||||
ApiError::ReqwestError(err)
|
||||
})?;
|
||||
|
||||
if !status.is_success() {
|
||||
error!("Failed to generate bearer token with session cookies: {} ; body = {}", status, response_body);
|
||||
let error = classify_general_http_status_code_and_body(status, &response_body).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
debug!("Bearer token generation response was 200.");
|
||||
debug!("Auth Response: {}", response_body);
|
||||
|
||||
if response_body == "null" {
|
||||
error!("Failed to generate bearer token. Response was the string `null`.");
|
||||
return Err(SoraError::FailedToGenerateBearer)
|
||||
}
|
||||
|
||||
let auth_response: SoraAuthResponse = serde_json::from_str(&response_body)?;
|
||||
|
||||
Ok(auth_response.access_token)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod get_session_request;
|
||||
Reference in new issue
Block a user