use reqwest::{self, multipart}; use std::error::Error; struct PixelfedConfig { pixelfed_url: String, pixelfed_access_token: String, pixelfed_visibility: String, // Should be "unlisted" pixelfed_default_text: String, pixelfed_batch_size: usize, } fn format_post_text(template: &str, batch_num: usize, total_batches: usize, title: &str) -> String { template .replace("@batch@", &format!("Batch {} out of {}", batch_num, total_batches)) .replace("@title@", title) } // upload a single image to pixelfed pub async fn bulk_upload_images(config: &super::Config, images: &[String], batch_num: usize, total_batches: usize, title: &str, caption_mode: &super::Mode) -> Result<(), Box> { let mut media_ids = Vec::new(); let mut media_descriptions = Vec::new(); let client = reqwest::Client::new(); let pxl_config = PixelfedConfig { pixelfed_url: config.pixelfed_url.clone(), pixelfed_access_token: config.pixelfed_access_token.clone(), pixelfed_visibility: config.pixelfed_visibility.clone(), pixelfed_default_text: config.pixelfed_default_text.clone(), pixelfed_batch_size: config.pixelfed_batch_size, }; let url = format!("{}/api/v1/media", config.pixelfed_url.clone()); for image_path in images { let description: String; println!("Uploading image {}", image_path.to_string()); // get image description depending on the caption_mode match caption_mode { super::Mode::ChatGPT => { let im_config = super::image_description::ChatGPTConfig { openai_model: config.openai_model.clone(), openai_api_key: config.openai_api_key.clone(), openai_api_url: config.openai_api_url.clone(), }; description = super::image_description::get_description_from_chatgpt(image_path.to_string(), im_config).await?; media_descriptions.push(description.clone()); }, super::Mode::File => { let im_config = super::image_description::FileConfig { caption_extension: config.caption_extension.clone(), }; println!("Fetching image description from File for {}", image_path.to_string()); description = super::image_description::get_description_from_file(image_path.to_string(), im_config).await?; media_descriptions.push(description.clone()); }, super::Mode::Local => { let im_config = super::image_description::OllamaConfig { ollama_api_key: config.ollama_api_key.clone(), ollama_api_url: config.ollama_api_url.clone(), ollama_model: config.ollama_model.clone(), }; println!("Fetching image description from OLLAMA for {}", image_path.to_string()); description = super::image_description::get_description_from_ollama(image_path.to_string(), im_config).await?; media_descriptions.push(description.clone()); }, } println!("Uploading image {}", image_path.to_string()); // construct the upload form for Pixelfed Upload of a single image including image description let form = multipart::Form::new() .file("file", image_path).await?; // upload the form to Pixelfed let res = client.post(&url) .bearer_auth(&config.pixelfed_access_token) .multipart(form) .send() .await?; let res_json: serde_json::Value = res.json().await?; let image_id = res_json["id"].as_str().unwrap().to_string(); media_ids.push(image_id); } if !media_ids.is_empty() { let post_url = format!("{}/api/v1/statuses", config.pixelfed_url); let post_text = format_post_text(&config.pixelfed_default_text, batch_num, total_batches, title); let body = serde_json::json!({ "status": post_text, "media_ids": media_ids, "alt_texts": media_descriptions, "visibility": config.pixelfed_visibility, }); println!("Body: \n{}", body.to_string()); println!("MediaIDs: {}", media_ids.len()); println!("Alt_texts: {}", media_descriptions.len()); println!("Posting batch {} out of {} with media {}", batch_num, total_batches, media_ids.len()); client.post(&post_url) .bearer_auth(&config.pixelfed_access_token) .json(&body) .send().await?; } Ok(()) }