diff --git a/crates/api_clients/grok_client/src/recipes/metadata/video_gen_request_flows.md b/crates/api_clients/grok_client/src/recipes/metadata/video_gen_request_flows.md index 549c58e5c4..b7bc0814a0 100644 --- a/crates/api_clients/grok_client/src/recipes/metadata/video_gen_request_flows.md +++ b/crates/api_clients/grok_client/src/recipes/metadata/video_gen_request_flows.md @@ -68,6 +68,15 @@ Generate Image -> Video Flow (TODO) +2. Create Media Post + + Request + { + "mediaType": "MEDIA_POST_TYPE_IMAGE", + "mediaUrl":"https://imagine-public.x.ai/imagine-public/images/2d8260d7-3b7c-4bc1-84ca-6e82dd892c39.png" + } + + 3. Chat Conversation for Video Endpoint: https://grok.com/rest/app-chat/conversations/new diff --git a/crates/api_clients/grok_client/src/requests/create_media_post/grok_create_media_post.rs b/crates/api_clients/grok_client/src/requests/create_media_post/grok_create_media_post.rs new file mode 100644 index 0000000000..ff33c1c6f4 --- /dev/null +++ b/crates/api_clients/grok_client/src/requests/create_media_post/grok_create_media_post.rs @@ -0,0 +1,161 @@ +use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT; +use crate::error::grok_client_error::GrokClientError; +use crate::error::grok_error::GrokError; +use crate::error::grok_generic_api_error::GrokGenericApiError; +use crate::requests::create_media_post::request::CreateMediaPostWireRequest; +use crate::requests::upload_file::grok_upload_file::{FileSpec, GrokUploadFile, GrokUploadFileResponse}; +use crate::types::file_id::FileId; +use crate::types::user_id::UserId; +use crate::utils::user_and_file_id_to_image_url::user_and_file_id_to_image_url; +use log::{error, info}; +use std::time::Duration; +use wreq::header::{ACCEPT, ACCEPT_LANGUAGE, CACHE_CONTROL, CONTENT_TYPE, COOKIE, ORIGIN, PRAGMA, REFERER, USER_AGENT}; +use wreq::Client; +use wreq_util::Emulation; + +const CREATE_MEDIA_POST_URL : &str = "https://grok.com/rest/media/post/create"; + +/// Request builder +pub struct GrokCreateMediaPost<'a> { + pub user_id: &'a UserId, + pub file_id: &'a FileId, + pub media_type: MediaPostType, + pub cookie: &'a str, + pub request_timeout: Option, +} + +#[derive(Debug, Copy, Clone)] +pub enum MediaPostType { + UserUploadedImage, + GrokGeneratedImage, + Video, +} + +/// Response type +#[derive(Debug)] +pub struct GrokCreateMediaPostResponse { +} + +impl <'a> GrokCreateMediaPost<'a> { + pub async fn send(&self) -> Result { + info!("Configuring client..."); + + let client = Client::builder() + .emulation(Emulation::Firefox143) + .build() + .map_err(|err| GrokClientError::WreqClientError(err))?; + + // TODO: Headers were from Chromium, not Firefox. Partial implementation. + let mut request_builder = client.post(CREATE_MEDIA_POST_URL) + .header(ACCEPT, "*/*") + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") + // TODO: Missing header "baggage" + .header(CACHE_CONTROL, "no-cache") + .header(CONTENT_TYPE, "application/json") + .header(COOKIE, self.cookie.to_string()) + .header(ORIGIN, "https://grok.com") + .header(PRAGMA, "no-cache") + .header("priority", "u=1, i") + .header(REFERER, "https://grok.com/imagine/favorites") + //.header("sec-ch-ua", "") // TODO + //.header("sec-ch-ua-mobile", "") // TODO + //.header("sec-ch-ua-platform", "") // TODO + .header("sec-fetch-dest", "empty") + .header("sec-fetch-mode", "cors") + .header("sec-fetch-site", "same-origin") + // TODO: Missing header "sentry-trace" + // TODO: Missing header "traceparent" + // TODO: Missing header "traceparent" + // TODO: Missing header "x-statsig-id" + // TODO: Missing header "x-xai-request-id" + .header(USER_AGENT, FIREFOX_143_MAC_USER_AGENT); + + //info!("Sending..."); + //let response = builder.send() + // .await + // .map_err(|err| GrokClientError::WreqClientError(err))?; + + if let Some(timeout) = self.request_timeout { + request_builder = request_builder.timeout(timeout); + } + + let media_url = match self.media_type { + MediaPostType::UserUploadedImage => user_and_file_id_to_image_url(self.user_id, self.file_id), + MediaPostType::GrokGeneratedImage => unimplemented!("implement for generated images"), + MediaPostType::Video => unimplemented!("implement for videos"), + }; + + let request_body = CreateMediaPostWireRequest { + media_type: "MEDIA_POST_TYPE_IMAGE".to_string(), + media_url, + }; + + let http_request = request_builder.json(&request_body) + .build() + .map_err(|err| { + error!("Error building create media request: {:?}", err); + GrokClientError::WreqClientError(err) + })?; + + let response = client.execute(http_request) + .await + .map_err(|err| { + error!("Error during create media: {:?}", err); + GrokGenericApiError::WreqError(err) + })?; + + let status = response.status(); + + let response_body = &response.text() + .await + .map_err(|err| { + error!("Error reading Grok create media response body: {:?}", err); + GrokGenericApiError::WreqError(err) + })?; + + println!("Body: {}", response_body); + + // TODO: + //if !status.is_success() { + // error!("Upload file request returned an error (code {}) : {:?}", status.as_u16(), response_body); + // return Err(classify_general_http_status_code_and_body(status, response_body)); + //} + + //let response : GrokApiUploadFileResponse = serde_json::from_str(response_body) + // .map_err(|err| GrokGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?; + + Ok(GrokCreateMediaPostResponse{ + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::get_test_cookies::get_test_cookies; + use errors::AnyhowResult; + + #[tokio::test] + #[ignore] + async fn create_media_post() -> AnyhowResult<()> { + //setup_test_logging(LevelFilter::Trace); + let cookies = get_test_cookies()?; + + let user_id = UserId("85980643-ffab-4984-a3de-59a608c47d7f".to_string()); // User + let file_id = FileId("990ddf90-8f34-42b1-81a5-39c509d62ff7".to_string()); // Mochi + + let request = GrokCreateMediaPost { + user_id: &user_id, + file_id: &file_id, + media_type: MediaPostType::UserUploadedImage, + cookie: &cookies, + request_timeout: None, + }; + + let result = request.send().await?; + + println!("Result: {:?}", result); + assert_eq!(1, 2); + Ok(()) + } +} diff --git a/crates/api_clients/grok_client/src/requests/create_media_post/mod.rs b/crates/api_clients/grok_client/src/requests/create_media_post/mod.rs index e69de29bb2..54b3142077 100644 --- a/crates/api_clients/grok_client/src/requests/create_media_post/mod.rs +++ b/crates/api_clients/grok_client/src/requests/create_media_post/mod.rs @@ -0,0 +1,2 @@ +mod grok_create_media_post; +mod request; \ No newline at end of file diff --git a/crates/api_clients/grok_client/src/requests/create_media_post/request.rs b/crates/api_clients/grok_client/src/requests/create_media_post/request.rs new file mode 100644 index 0000000000..e812cb4345 --- /dev/null +++ b/crates/api_clients/grok_client/src/requests/create_media_post/request.rs @@ -0,0 +1,10 @@ +use serde::Serialize; + +#[derive(Serialize)] +pub (super) struct CreateMediaPostWireRequest { + #[serde(rename = "mediaType")] + pub (super) media_type: String, + + #[serde(rename = "mediaUrl")] + pub (super) media_url: String, +} diff --git a/crates/api_clients/grok_client/src/requests/upload_file/grok_upload_file.rs b/crates/api_clients/grok_client/src/requests/upload_file/grok_upload_file.rs index 3c9d9426e8..31c669c141 100644 --- a/crates/api_clients/grok_client/src/requests/upload_file/grok_upload_file.rs +++ b/crates/api_clients/grok_client/src/requests/upload_file/grok_upload_file.rs @@ -22,7 +22,8 @@ const UPLOAD_FILE_URL : &str = "https://grok.com/rest/app-chat/upload-file"; /// There's a better way to implement this. const INITIAL_BUFFER_SIZE : usize = 1024*1024; -pub struct GrokUploadFileRequest<'a, P: AsRef> { +/// Request builder +pub struct GrokUploadFile<'a, P: AsRef> { pub file: FileSpec<'a, P>, pub cookie: String, pub request_timeout: Option, @@ -43,13 +44,14 @@ pub enum FileSpec<'a, P: AsRef> { } } +/// Response type #[derive(Clone, Debug)] pub struct GrokUploadFileResponse { pub file_metadata_id: Option, pub file_uri: Option, } -impl <'a, P> GrokUploadFileRequest<'a, P> where P: AsRef { +impl <'a, P> GrokUploadFile<'a, P> where P: AsRef { pub async fn upload(&self) -> Result { match &self.file { @@ -210,7 +212,7 @@ mod tests { async fn upload_file() -> AnyhowResult<()> { //setup_test_logging(LevelFilter::Trace); let cookies = get_test_cookies()?; - let request = GrokUploadFileRequest { + let request = GrokUploadFile { file: FileSpec::Path("/Users/bt/dev/storyteller/storyteller-rust/test_data/image/mochi.jpg"), cookie: cookies.to_string(), request_timeout: None, @@ -222,4 +224,4 @@ mod tests { assert_eq!(1, 2); Ok(()) } -} \ No newline at end of file +} diff --git a/crates/api_clients/grok_client/src/utils/mod.rs b/crates/api_clients/grok_client/src/utils/mod.rs index 3980e3340e..2d77e4f7c4 100644 --- a/crates/api_clients/grok_client/src/utils/mod.rs +++ b/crates/api_clients/grok_client/src/utils/mod.rs @@ -1,2 +1,2 @@ -mod user_and_file_id_to_image_url; -mod user_and_file_id_to_video_url; \ No newline at end of file +pub mod user_and_file_id_to_image_url; +pub mod user_and_file_id_to_video_url; \ No newline at end of file