mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
persist sentinel token in wrapper
This commit is contained in:
1 parent
c60770e881
commit
7842b3bd95
9 files changed
+234
-50
No files matched your search
@@ -2,4 +2,5 @@ pub mod sora_cookies;
|
||||
pub mod sora_credential_builder;
|
||||
pub mod sora_credential_set;
|
||||
pub mod sora_jwt_bearer_token;
|
||||
pub mod sora_sentinel;
|
||||
pub mod sora_sentinel;
|
||||
pub mod sora_sentinel_token;
|
||||
@@ -2,13 +2,20 @@ use crate::creds::sora_cookies::SoraCookies;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::creds::sora_sentinel::SoraSentinel;
|
||||
use crate::creds::sora_sentinel_token::SoraSentinelToken;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
|
||||
pub struct SoraCredentialBuilder {
|
||||
cookies: Option<String>,
|
||||
|
||||
jwt_bearer_token: Option<String>,
|
||||
|
||||
#[deprecated(note="use sora_sentinel_token instead")]
|
||||
sora_sentinel: Option<String>,
|
||||
|
||||
/// This is the newer sentinel token. Gradually phase out the older one.
|
||||
sora_sentinel_token: Option<SoraSentinelToken>,
|
||||
}
|
||||
|
||||
impl SoraCredentialBuilder {
|
||||
@@ -17,6 +24,7 @@ impl SoraCredentialBuilder {
|
||||
cookies: None,
|
||||
jwt_bearer_token: None,
|
||||
sora_sentinel: None,
|
||||
sora_sentinel_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +45,11 @@ impl SoraCredentialBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sora_sentinel_token(mut self, sentinel_token: &SoraSentinelToken) -> Self {
|
||||
self.sora_sentinel_token = Some(sentinel_token.clone());
|
||||
self
|
||||
}
|
||||
|
||||
// Mutable methods
|
||||
|
||||
pub fn set_cookies(&mut self, cookies: &str) {
|
||||
@@ -50,6 +63,10 @@ impl SoraCredentialBuilder {
|
||||
pub fn set_sora_sentinel(&mut self, sentinel: &str) {
|
||||
self.sora_sentinel = Some(sentinel.to_string());
|
||||
}
|
||||
|
||||
pub fn set_sora_sentinel_token(&mut self, sentinel_token: &SoraSentinelToken) {
|
||||
self.sora_sentinel_token = Some(sentinel_token.clone());
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<SoraCredentialSet, SoraError> {
|
||||
let cookies = match self.cookies {
|
||||
@@ -67,10 +84,16 @@ impl SoraCredentialBuilder {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let sora_sentinel_token = match self.sora_sentinel_token {
|
||||
Some(s) => Some(s),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(SoraCredentialSet {
|
||||
cookies,
|
||||
jwt_bearer_token,
|
||||
sora_sentinel,
|
||||
sora_sentinel_token,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,32 @@ use crate::creds::sora_cookies::SoraCookies;
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::creds::sora_sentinel::SoraSentinel;
|
||||
use crate::creds::sora_sentinel_token::SoraSentinelToken;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SoraCredentialSet {
|
||||
pub cookies: SoraCookies,
|
||||
pub jwt_bearer_token: Option<SoraJwtBearerToken>,
|
||||
|
||||
#[deprecated(note="use sora_sentinel_token instead")]
|
||||
pub sora_sentinel: Option<SoraSentinel>,
|
||||
|
||||
// NB: This is the newer sentinel token. Gradually phase out the older one.
|
||||
pub sora_sentinel_token: Option<SoraSentinelToken>,
|
||||
}
|
||||
|
||||
impl SoraCredentialSet {
|
||||
pub fn initialize(
|
||||
cookies: SoraCookies,
|
||||
bearer: Option<SoraJwtBearerToken>,
|
||||
sentinel: Option<SoraSentinel>
|
||||
sentinel: Option<SoraSentinel>,
|
||||
sentinel_token: Option<SoraSentinelToken>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cookies,
|
||||
jwt_bearer_token: bearer,
|
||||
sora_sentinel: sentinel,
|
||||
sora_sentinel_token: sentinel_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +36,7 @@ impl SoraCredentialSet {
|
||||
cookies,
|
||||
jwt_bearer_token: None,
|
||||
sora_sentinel: None,
|
||||
sora_sentinel_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +55,9 @@ impl SoraCredentialSet {
|
||||
if let Some(sentinel) = &self.sora_sentinel {
|
||||
builder = builder.with_sora_sentinel(sentinel.as_str());
|
||||
}
|
||||
if let Some(sentinel_token) = &self.sora_sentinel_token {
|
||||
builder = builder.with_sora_sentinel_token(sentinel_token);
|
||||
}
|
||||
builder
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
/// Sora Sentinel tokens can be generated without cookies or JWTs, and are needed for some API calls.
|
||||
#[derive(Clone)]
|
||||
#[deprecated(note="use SoraSentinelToken instead")]
|
||||
pub struct SoraSentinel {
|
||||
sentinel: String,
|
||||
|
||||
@@ -13,14 +14,17 @@ impl SoraSentinel {
|
||||
SoraSentinel { sentinel }
|
||||
}
|
||||
|
||||
#[deprecated(note="use SoraSentinelToken instead")]
|
||||
pub fn get_sentinel(&self) -> &str {
|
||||
&self.sentinel
|
||||
}
|
||||
|
||||
#[deprecated(note="use SoraSentinelToken instead")]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.sentinel
|
||||
}
|
||||
|
||||
#[deprecated(note="use SoraSentinelToken instead")]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.sentinel.as_bytes()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SoraSentinelToken {
|
||||
/// The token sent over the wire.
|
||||
/// (The other fields in this struct are metadata.)
|
||||
pub token: RawSoraSentinelToken,
|
||||
|
||||
/// User agent used to get the token.
|
||||
pub browser_user_agent: String,
|
||||
|
||||
/// Clientside time when it was generated (client-side).
|
||||
pub generated_at: DateTime<Utc>,
|
||||
|
||||
/// Expiration time (reported by the server)
|
||||
/// If the server doesn't tell us, we'll fill it in with our best guess.
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
|
||||
/// Seconds of validity (reported by the server)
|
||||
/// If the server doesn't tell us, we'll fill it in with our best guess.
|
||||
pub expires_in_seconds: Option<u32>,
|
||||
}
|
||||
|
||||
/// This is what is sent over the wire in requests.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawSoraSentinelToken {
|
||||
/// "Proof of Work" token (we generated this clientside)
|
||||
#[serde(rename = "p")]
|
||||
pub p: String,
|
||||
|
||||
/// Random UUID (we generated this clientside)
|
||||
#[serde(rename = "id")]
|
||||
pub id: String,
|
||||
|
||||
/// Frontend UI flow (this is the website flow used)
|
||||
#[serde(rename = "flow")]
|
||||
pub flow: String,
|
||||
|
||||
/// Turnstile dx from the response.
|
||||
/// response["turnstile"]["dx"]
|
||||
#[serde(rename = "t")]
|
||||
pub t: String,
|
||||
|
||||
/// Token from the response.
|
||||
/// response["token"]
|
||||
#[serde(rename = "c")]
|
||||
pub c: String,
|
||||
}
|
||||
|
||||
impl SoraSentinelToken {
|
||||
pub fn is_expired(&self) -> bool {
|
||||
// NB(1): We're not trusting the server timestamp for now.
|
||||
// NB(2): We're not self-expiring for now.
|
||||
let now = Utc::now();
|
||||
|
||||
if let Some(expires_in_seconds) = self.expires_in_seconds {
|
||||
let maybe_expires_at = now
|
||||
.checked_add_signed(TimeDelta::seconds(expires_in_seconds as i64));
|
||||
|
||||
if let Some(expires_at) = maybe_expires_at {
|
||||
if now >= expires_at {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Convert to JSON, which is the raw over-the-wire format Sora consumes.
|
||||
pub fn to_request_header_json(&self) -> Result<String, SoraClientError> {
|
||||
self.token.to_request_header_json()
|
||||
}
|
||||
|
||||
pub fn to_persistent_storage_json(&self) -> Result<String, SoraClientError> {
|
||||
serde_json::to_string(self)
|
||||
.map_err(|err| SoraClientError::CouldNotSerializeSentinelTokenStore(err))
|
||||
}
|
||||
|
||||
pub fn from_persistent_storage_json(json: &str) -> Result<Self, SoraClientError> {
|
||||
serde_json::from_str(json)
|
||||
.map_err(|err| SoraClientError::CouldNotDeserializeSentinelTokenStore(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl RawSoraSentinelToken {
|
||||
/// Convert to JSON, which is the raw over-the-wire format Sora consumes.
|
||||
pub fn to_request_header_json(&self) -> Result<String, SoraClientError> {
|
||||
serde_json::to_string(self)
|
||||
.map_err(|err| SoraClientError::CouldNotSerializeSentinelToken(err))
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,12 @@ pub enum SoraClientError {
|
||||
|
||||
/// Error serializing the sentinel token to JSON
|
||||
CouldNotSerializeSentinelToken(serde_json::Error),
|
||||
|
||||
/// 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(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Error for SoraClientError {}
|
||||
@@ -52,6 +58,8 @@ impl Display for SoraClientError {
|
||||
Self::UrlParseError(err) => write!(f, "URL parse error: {}", err),
|
||||
Self::WreqClientError(err) => write!(f, "Wreq client error (during client creation): {}", 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(err) => write!(f, "Could not deserialize sentinel token store from JSON: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
-28
@@ -1,11 +1,12 @@
|
||||
use crate::constants::user_agent::CLIENT_USER_AGENT;
|
||||
use crate::creds::sora_sentinel_token::{RawSoraSentinelToken, SoraSentinelToken};
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::error::sora_specific_api_error::SoraSpecificApiError;
|
||||
use crate::requests::auth_sentinel::request::GenerateSentinelRefreshRequest;
|
||||
use crate::requests::auth_sentinel_2::request::SentinelRequest;
|
||||
use crate::requests::auth_sentinel_2::response::SentinelResponse;
|
||||
use crate::requests::auth_sentinel_2::sentinel_token::{SentinelResponsePieces, SentinelToken};
|
||||
use crate::requests::auth_sentinel_2::sentinel_token::SentinelResponsePieces;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use errors::AnyhowResult;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
@@ -21,7 +22,7 @@ const SORA_SENTINEL_ENDPOINT: &str = "https://chatgpt.com/backend-api/sentinel/r
|
||||
|
||||
|
||||
/// This generates a Sentinel Token that works with Sora 1.0 and Sora 2.0 consumer products.
|
||||
pub async fn generate_sentinel_token_2() -> Result<SentinelToken, SoraError> {
|
||||
pub async fn generate_sentinel_token_2() -> Result<SoraSentinelToken, SoraError> {
|
||||
let (_request, base64_request) = GenerateSentinelRefreshRequest::new().with_fourth_and_tenth();
|
||||
|
||||
let request = SentinelRequest::new(base64_request);
|
||||
@@ -72,49 +73,75 @@ pub async fn generate_sentinel_token_2() -> Result<SentinelToken, SoraError> {
|
||||
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, text_body.to_string())
|
||||
})?;
|
||||
|
||||
let response_pieces = sentinel_response_into_pieces(response, text_body)?;
|
||||
let sentinel_token = SentinelToken::from_server_request(&request, &response_pieces);
|
||||
let sentinel_token = into_sora_sentinel_token(request, response, text_body)?;
|
||||
|
||||
Ok(sentinel_token)
|
||||
}
|
||||
|
||||
|
||||
fn sentinel_response_into_pieces(response: SentinelResponse, raw_body: &str) -> Result<SentinelResponsePieces, SoraSpecificApiError> {
|
||||
|
||||
fn into_sora_sentinel_token(request: SentinelRequest, response: SentinelResponse, raw_body: &str) -> Result<SoraSentinelToken, SoraSpecificApiError> {
|
||||
let maybe_token = response.token;
|
||||
|
||||
let maybe_turnstile = response.turnstile
|
||||
.map(|turn| turn.dx)
|
||||
.flatten();
|
||||
|
||||
let mut missing_token = false;
|
||||
let mut missing_turnstile = false;
|
||||
let mut missing_turnstile = false;
|
||||
|
||||
match (response.token, maybe_turnstile) {
|
||||
(None, None) => {
|
||||
missing_token = true;
|
||||
missing_turnstile = true;
|
||||
let (token, turnstile) = {
|
||||
let maybe_items = match (maybe_token, maybe_turnstile) {
|
||||
(Some(token), Some(turnstile)) => {
|
||||
Some((token, turnstile))
|
||||
}
|
||||
(None, None) => {
|
||||
missing_token = true;
|
||||
missing_turnstile = true;
|
||||
None
|
||||
}
|
||||
(None, Some(_turnstile)) => {
|
||||
missing_token = true;
|
||||
None
|
||||
}
|
||||
(Some(_token), None) => {
|
||||
missing_turnstile = true;
|
||||
None
|
||||
}
|
||||
};
|
||||
match maybe_items {
|
||||
Some((token, turnstile)) => (token, turnstile),
|
||||
_ => return Err(SoraSpecificApiError::SentinelResponseIsMissingFields {
|
||||
missing_token,
|
||||
missing_turnstile,
|
||||
raw_response: raw_body.to_string(),
|
||||
}),
|
||||
}
|
||||
(None, Some(_turnstile)) => {
|
||||
missing_token = true;
|
||||
}
|
||||
(Some(_token), None) => {
|
||||
missing_turnstile = true;
|
||||
}
|
||||
(Some(token), Some(turnstile)) => {
|
||||
return Ok(SentinelResponsePieces {
|
||||
token,
|
||||
turnstile_dx: turnstile,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(SoraSpecificApiError::SentinelResponseIsMissingFields {
|
||||
missing_token,
|
||||
missing_turnstile,
|
||||
raw_response: raw_body.to_string(),
|
||||
})
|
||||
let sentinel_raw = RawSoraSentinelToken {
|
||||
p: request.p,
|
||||
id: request.id,
|
||||
flow: request.flow,
|
||||
t: turnstile,
|
||||
c: token,
|
||||
};
|
||||
|
||||
let sentinel = SoraSentinelToken {
|
||||
token: sentinel_raw,
|
||||
browser_user_agent: CLIENT_USER_AGENT.to_string(),
|
||||
generated_at: Default::default(), // TODO
|
||||
expires_at: None, // TODO
|
||||
expires_in_seconds: None, // TODO
|
||||
};
|
||||
|
||||
Ok(sentinel)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::write;
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -125,4 +152,19 @@ mod tests {
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Only manually trigger this
|
||||
async fn write_token_to_disk() -> AnyhowResult<()> {
|
||||
let filename = "/Users/bt/Artcraft/credentials/sora_sentinel_token_store.json";
|
||||
let token = generate_sentinel_token_2().await?;
|
||||
|
||||
let json = token.to_persistent_storage_json()?;
|
||||
|
||||
write(filename, &json)?;
|
||||
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
+11
-18
@@ -3,6 +3,7 @@ use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::auth_sentinel_2::generate_sentinel_token_2::generate_sentinel_token_2;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::generate_sora2_video::http_request::{HttpCreateRequest, InpaintItem};
|
||||
use crate::requests::generate_sora2_video::http_response::HttpCreateResponse;
|
||||
@@ -13,7 +14,6 @@ use std::io::Write;
|
||||
use std::time::Duration;
|
||||
use wreq::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_TYPE, COOKIE, ORIGIN, REFERER, USER_AGENT};
|
||||
use wreq::Client;
|
||||
use crate::requests::auth_sentinel_2::generate_sentinel_token_2::generate_sentinel_token_2;
|
||||
|
||||
const GENERATE_SORA_2_VIDEO_URL: &str = "https://sora.chatgpt.com/backend/nf/create";
|
||||
|
||||
@@ -47,21 +47,18 @@ pub (crate) async fn generate_sora2_video(
|
||||
.ok_or(SoraClientError::NoBearerTokenForRequest)?
|
||||
.to_authorization_header_value();
|
||||
|
||||
let sentinel_token = generate_sentinel_token_2().await?;
|
||||
|
||||
let sentinel_token = sentinel_token.to_json()?;
|
||||
|
||||
//let sentinel_token = args.credentials.sora_sentinel.as_ref()
|
||||
// .map(|sentinel| sentinel.get_sentinel().to_string())
|
||||
// .ok_or(SoraClientError::NoSentinelTokenForRequest)?;
|
||||
let sentinel_token = args.credentials.sora_sentinel_token
|
||||
.as_ref()
|
||||
.ok_or(SoraClientError::NoSentinelTokenForRequest)?;
|
||||
|
||||
let sentinel_token = sentinel_token.to_request_header_json()?;
|
||||
|
||||
let cookie = args.credentials.cookies.to_string();
|
||||
|
||||
// TODO: Make the sec-* headers match the user agent and platform
|
||||
// TODO: No sentinel?
|
||||
// TODO: device id
|
||||
//-H 'oai-device-id: 7c216860-5b73-4e63-983f-142dbcae1809' \
|
||||
let mut http_request = client.post(GENERATE_SORA_2_VIDEO_URL)
|
||||
let mut request_builder = client.post(GENERATE_SORA_2_VIDEO_URL)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header("priority", "u=1, i")
|
||||
.header(REFERER, "https://sora.chatgpt.com/explore")
|
||||
@@ -80,7 +77,7 @@ pub (crate) async fn generate_sora2_video(
|
||||
.header("sec-fetch-site", "same-origin");
|
||||
|
||||
if let Some(timeout) = args.request_timeout {
|
||||
http_request = http_request.timeout(timeout);
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
let orientation = match args.orientation {
|
||||
@@ -119,15 +116,13 @@ pub (crate) async fn generate_sora2_video(
|
||||
storyboard_id: None,
|
||||
};
|
||||
|
||||
let http_request = http_request.json(&request_body).build()
|
||||
let http_request = request_builder.json(&request_body)
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
error!("Error building Sora image generation HTTP request: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
println!("send request...");
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -135,8 +130,6 @@ pub (crate) async fn generate_sora2_video(
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
println!("read status...");
|
||||
std::io::stdout().flush().unwrap();
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text().await
|
||||
@@ -170,7 +163,7 @@ mod tests {
|
||||
let creds = get_test_credentials()?;
|
||||
let request = GenerateSora2VideoArgs {
|
||||
//prompt: "A cute corgi wearing glasses, sitting on a picnic blanket and reading a book, digital art",
|
||||
prompt: "A dog eating waffles in a Waffle House",
|
||||
prompt: "A dog eating pancakes in an iHop",
|
||||
credentials: &creds,
|
||||
request_timeout: None,
|
||||
orientation: Orientation::Landscape,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_sentinel_token::SoraSentinelToken;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
@@ -8,7 +9,12 @@ pub fn get_test_credentials() -> AnyhowResult<SoraCredentialSet> {
|
||||
// TODO: This should work on other folks' machines when they exist
|
||||
// This is so brittle...
|
||||
let sentinel = read_to_string("/Users/bt/Artcraft/credentials/sora_sentinel.txt")?;
|
||||
let sentinel = sentinel.trim().to_string();
|
||||
|
||||
let sentinel_token = {
|
||||
let sentinel_json = read_to_string("/Users/bt/Artcraft/credentials/sora_sentinel_token_store.json")?;
|
||||
let sentinel_json = sentinel.trim().to_string();
|
||||
SoraSentinelToken::from_persistent_storage_json(&sentinel_json)?
|
||||
};
|
||||
|
||||
let cookie = read_to_string("/Users/bt/Artcraft/credentials/sora_cookies.txt")?;
|
||||
let cookie = cookie.trim().to_string();
|
||||
@@ -20,7 +26,8 @@ pub fn get_test_credentials() -> AnyhowResult<SoraCredentialSet> {
|
||||
.with_cookies(&cookie)
|
||||
.with_jwt_bearer_token(&bearer)
|
||||
.with_sora_sentinel(&sentinel)
|
||||
.with_sora_sentinel_token(&sentinel_token)
|
||||
.build()?;
|
||||
|
||||
Ok(creds)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user