From b3f15e3cd6076ff8e44053ff7f5205faf8b90291 Mon Sep 17 00:00:00 2001 From: Falko Zurell Date: Mon, 18 Nov 2024 21:10:44 +0100 Subject: [PATCH] Initial working version --- .env.example | 9 +++ .gitignore | 0 Cargo.toml | 12 ++++ src/main.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c4abcc8 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# OpenAPI API +OPENAI_API_KEY=your_openai_api_key +OPENAI_API_URL=https://api.openai.com/v1/images/generate-description # Update this to the actual endpoint +OPENAI_MODEL=gpt-4-vision # Update to the model you want to use + +# WordPress API +WP_URL=https://yourwordpresssite.com +WP_USERNAME=your_wp_username +WP_PASSWORD=your_wp_application_password diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ae2e3f2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "image_wp_uploader" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +dotenvy = "0.15" +base64 = "0.21" # Use the latest version of the crate diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4d31cdf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,155 @@ +use std::env; +use std::fs::File; +use std::io::Read; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; // Import the Engine trait + +use reqwest::Client; +use serde_json::Value; + +#[derive(Debug)] +struct Config { + wp_url: String, + wp_username: String, + wp_password: String, + openai_api_url: String, + openai_api_key: String, + openai_model: String, +} + +async fn generate_description(config: &Config, image_data: &[u8]) -> Result> { + let client = Client::new(); + + // Encode the image data in base64 using STANDARD engine + let encoded_image = STANDARD.encode(image_data); + + // Call OpenAI API with gpt-4-turbo + let response = client + .post(&config.openai_api_url) + .header("Authorization", format!("Bearer {}", config.openai_api_key)) + .json(&serde_json::json!({ + "model": config.openai_model, + "messages": [ + { + "role": "system", + "content": "You are an AI assistant that describes images for blog posts." + }, + { + "role": "user", + "content": "Describe the content of this image.", + "image": encoded_image + } + ] + })) + .send() + .await?; + + let response_text = response.text().await?; + let json: Value = serde_json::from_str(&response_text)?; + + // Safely extract the description + let description = json["choices"] + .get(0) + .and_then(|choice| choice["message"]["content"].as_str()) + .unwrap_or("No description generated") + .to_string(); + + Ok(description) +} + +async fn upload_to_wordpress( + wp_url: &str, + wp_username: &str, + wp_password: &str, + image_data: &[u8], + filename: &str, + description: &str, +) -> Result<(), Box> { + let client = Client::new(); + + // Encode the authentication string using base64::engine + let auth = STANDARD.encode(format!("{}:{}", wp_username, wp_password)); + + // Upload image to WordPress + let response = client + .post(format!("{}/wp-json/wp/v2/media", wp_url)) + .header("Authorization", format!("Basic {}", auth)) + .header("Content-Disposition", format!("attachment; filename=\"{}\"", filename)) + .body(image_data.to_vec()) + .send() + .await?; + + let response_text = response.text().await?; + + // Deserialize the response as a JSON object (Value) + let json: Value = serde_json::from_str(&response_text)?; + + // Safely extract the media_id from the response + let media_id = json["id"] + .as_i64() + .unwrap_or(0) as i32; + + // Create a post with the image and description + client + .post(format!("{}/wp-json/wp/v2/posts", wp_url)) + .header("Authorization", format!("Basic {}", auth)) + .json(&serde_json::json!({ + "title": filename, + "content": description, + "status": "publish", + "featured_media": media_id, + })) + .send() + .await?; + + Ok(()) +} + +async fn process_image(config: &Config, image_path: &str) -> Result<(), Box> { + // Read the image file + let mut file = File::open(image_path)?; + let mut image_data = Vec::new(); + file.read_to_end(&mut image_data)?; + + // Generate description from OpenAI + let description = generate_description(config, &image_data).await?; + + // Upload image and create WordPress post + let filename = image_path.split('/').last().unwrap_or("image"); + upload_to_wordpress( + &config.wp_url, + &config.wp_username, + &config.wp_password, + &image_data, + filename, + &description, + ).await?; + + Ok(()) +} + +#[tokio::main] +async fn main() { + // Read command-line arguments + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: {} ", args[0]); + return; + } + + let image_path = &args[1]; + + // Load configuration from .env or configuration file + let config = Config { + wp_url: "https://falko.zurell.de".to_string(), + wp_username: "falko".to_string(), + wp_password: "G6FI qmWi OG1M vXqP 1p5j rDDS".to_string(), + openai_api_url: "https://api.openai.com/v1/chat/completions".to_string(), + openai_api_key: "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".to_string(), + openai_model: "gpt-4.0".to_string(), + }; + + if let Err(e) = process_image(&config, image_path).await { + eprintln!("Error processing image: {}", e); + } +}