first properly working Version
This commit is contained in:
parent
366c4c1660
commit
19c8a93412
3 changed files with 129 additions and 129 deletions
17
.env.example
17
.env.example
|
@ -1,9 +1,8 @@
|
||||||
# OpenAPI API
|
#WordPress API
|
||||||
OPENAI_API_KEY=your_openai_api_key
|
WP_URL=https://your.wordpress.com
|
||||||
OPENAI_API_URL=https://api.openai.com/v1/images/generate-description # Update this to the actual endpoint
|
WP_USERNAME=username
|
||||||
OPENAI_MODEL=gpt-4-vision # Update to the model you want to use
|
WP_APP_PASSWORD="ApplicationPassword"
|
||||||
|
# OpenAI API
|
||||||
# WordPress API
|
OPENAI_API_KEY="SecretApplicationKey"
|
||||||
WP_URL=https://yourwordpresssite.com
|
OPENAI_API_URL=https://api.openai.com/v1/chat/completions
|
||||||
WP_USERNAME=your_wp_username
|
OPENAI_MODEL=gpt-4o
|
||||||
WP_PASSWORD=your_wp_application_password
|
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -2,11 +2,9 @@
|
||||||
name = "image_wp_uploader"
|
name = "image_wp_uploader"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json", "multipart"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
dotenv = "0.15"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
dotenvy = "0.15"
|
base64 = "0.21"
|
||||||
base64 = "0.21" # Use the latest version of the crate
|
|
||||||
|
|
231
src/main.rs
231
src/main.rs
|
@ -1,155 +1,158 @@
|
||||||
|
use dotenv::dotenv;
|
||||||
|
use reqwest::{self, multipart};
|
||||||
|
use serde_json::{json, Value};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::error::Error;
|
||||||
use std::io::Read;
|
use std::path::Path;
|
||||||
use base64::engine::general_purpose::STANDARD;
|
use tokio;
|
||||||
use base64::Engine; // Import the Engine trait
|
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||||
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
wp_url: String,
|
wp_url: String,
|
||||||
wp_username: String,
|
wp_username: String,
|
||||||
wp_password: String,
|
wp_app_password: String,
|
||||||
openai_api_url: String,
|
|
||||||
openai_api_key: String,
|
openai_api_key: String,
|
||||||
|
openai_api_url: String,
|
||||||
openai_model: String,
|
openai_model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_description(config: &Config, image_data: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
|
impl Config {
|
||||||
let client = Client::new();
|
fn from_env() -> Result<Self, Box<dyn Error>> {
|
||||||
|
dotenv()?;
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
wp_url: env::var("WP_URL")?,
|
||||||
|
wp_username: env::var("WP_USERNAME")?,
|
||||||
|
wp_app_password: env::var("WP_APP_PASSWORD")?,
|
||||||
|
openai_api_key: env::var("OPENAI_API_KEY")?,
|
||||||
|
openai_api_url: env::var("OPENAI_API_URL")?,
|
||||||
|
openai_model: env::var("OPENAI_MODEL")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encode the image data in base64 using STANDARD engine
|
#[tokio::main]
|
||||||
let encoded_image = STANDARD.encode(image_data);
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Get image path from command line arguments
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() != 2 {
|
||||||
|
eprintln!("Usage: {} <image_path>", args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let image_path = &args[1];
|
||||||
|
|
||||||
// Call OpenAI API with gpt-4-turbo
|
// Load configuration
|
||||||
|
let config = Config::from_env()?;
|
||||||
|
|
||||||
|
// Get image description from ChatGPT
|
||||||
|
let description = get_image_description(&config, image_path).await?;
|
||||||
|
println!("Generated description: {}", description);
|
||||||
|
|
||||||
|
// Upload image to WordPress and set ALT text
|
||||||
|
upload_to_wordpress(&config, image_path, &description).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_image_description(config: &Config, image_path: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
// Read and encode image
|
||||||
|
let image_data = std::fs::read(image_path)?;
|
||||||
|
let base64_image = STANDARD.encode(&image_data);
|
||||||
|
|
||||||
|
// Create ChatGPT API request
|
||||||
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.post(&config.openai_api_url)
|
.post(&config.openai_api_url)
|
||||||
.header("Authorization", format!("Bearer {}", config.openai_api_key))
|
.header("Authorization", format!("Bearer {}", config.openai_api_key))
|
||||||
.json(&serde_json::json!({
|
.header("Content-Type", "application/json")
|
||||||
"model": config.openai_model,
|
.json(&json!({
|
||||||
|
"model": config.openai_model, // Now using the model from config
|
||||||
|
"max_tokens": 300,
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
|
||||||
"role": "system",
|
|
||||||
"content": "You are an AI assistant that describes images for blog posts."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Describe the content of this image.",
|
"content": [
|
||||||
"image": encoded_image
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Please describe this image concisely for use as an alt text description. Focus on key visual elements and context."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url": {
|
||||||
|
"url": format!("data:image/jpeg;base64,{}", base64_image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response_text = response.text().await?;
|
// Improved error handling for API response
|
||||||
let json: Value = serde_json::from_str(&response_text)?;
|
if !response.status().is_success() {
|
||||||
|
let error_text = response.text().await?;
|
||||||
|
return Err(format!("OpenAI API error: {}", error_text).into());
|
||||||
|
}
|
||||||
|
|
||||||
// Safely extract the description
|
let result: Value = response.json().await?;
|
||||||
let description = json["choices"]
|
|
||||||
|
// More detailed error handling for JSON parsing
|
||||||
|
let description = result["choices"]
|
||||||
.get(0)
|
.get(0)
|
||||||
.and_then(|choice| choice["message"]["content"].as_str())
|
.ok_or("No choices in response")?
|
||||||
.unwrap_or("No description generated")
|
["message"]["content"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Invalid content format in response")?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok(description)
|
Ok(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload_to_wordpress(
|
async fn upload_to_wordpress(
|
||||||
wp_url: &str,
|
config: &Config,
|
||||||
wp_username: &str,
|
image_path: &str,
|
||||||
wp_password: &str,
|
|
||||||
image_data: &[u8],
|
|
||||||
filename: &str,
|
|
||||||
description: &str,
|
description: &str,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let client = Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
let file_data = tokio::fs::read(image_path).await?;
|
||||||
// Encode the authentication string using base64::engine
|
let filename = Path::new(image_path)
|
||||||
let auth = STANDARD.encode(format!("{}:{}", wp_username, wp_password));
|
.file_name()
|
||||||
|
.ok_or("Invalid filename")?
|
||||||
|
.to_str()
|
||||||
|
.ok_or("Invalid UTF-8 in filename")?;
|
||||||
|
|
||||||
// Upload image to WordPress
|
// Create form with image file
|
||||||
|
let part = multipart::Part::bytes(file_data)
|
||||||
|
.file_name(filename.to_string())
|
||||||
|
.mime_str("image/jpeg")?;
|
||||||
|
|
||||||
|
let form = multipart::Form::new()
|
||||||
|
.part("file", part)
|
||||||
|
.text("alt_text", description.to_string())
|
||||||
|
.text("description", description.to_string());
|
||||||
|
|
||||||
|
// Upload to WordPress
|
||||||
let response = client
|
let response = client
|
||||||
.post(format!("{}/wp-json/wp/v2/media", wp_url))
|
.post(format!("{}/wp-json/wp/v2/media", config.wp_url))
|
||||||
.header("Authorization", format!("Basic {}", auth))
|
.basic_auth(&config.wp_username, Some(&config.wp_app_password))
|
||||||
.header("Content-Disposition", format!("attachment; filename=\"{}\"", filename))
|
.multipart(form)
|
||||||
.body(image_data.to_vec())
|
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response_text = response.text().await?;
|
let status = response.status();
|
||||||
|
if status.is_success() {
|
||||||
// Deserialize the response as a JSON object (Value)
|
let response_json: Value = response.json().await?;
|
||||||
let json: Value = serde_json::from_str(&response_text)?;
|
println!("Successfully uploaded image to WordPress with description");
|
||||||
|
if let Some(url) = response_json["source_url"].as_str() {
|
||||||
// Safely extract the media_id from the response
|
println!("Image URL: {}", url);
|
||||||
let media_id = json["id"]
|
}
|
||||||
.as_i64()
|
} else {
|
||||||
.unwrap_or(0) as i32;
|
let error_text = response.text().await?;
|
||||||
|
println!("Failed to upload image: {}", status);
|
||||||
// Create a post with the image and description
|
println!("Response: {}", error_text);
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_image(config: &Config, image_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// 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<String> = env::args().collect();
|
|
||||||
if args.len() < 2 {
|
|
||||||
eprintln!("Usage: {} <path_to_image>", 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue