From c0d428297ee13600b618bf3fce49b587f67b6f5b Mon Sep 17 00:00:00 2001 From: Falko Zurell Date: Fri, 17 Jan 2025 15:25:44 +0100 Subject: [PATCH] initial working version --- .gitignore | 1 + Cargo.toml | 10 ++++++ src/main.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..07dc495 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "pixelfed_batch_uploader" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +reqwest = { version = "0.11", features = ["blocking", "json", "multipart"] } +tokio = { version = "1", features = ["full"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1bdaffc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use std::fs; +use std::path::Path; +use serde::{Deserialize, Serialize}; +use reqwest::blocking::Client; +use std::error::Error; + +#[derive(Debug, Deserialize)] +struct Config { + pixelfed_url: String, + access_token: String, + visibility: String, // Should be "unlisted" + default_text: String, +} + +fn load_config() -> Result> { + let config_str = fs::read_to_string("config.json")?; + let config: Config = serde_json::from_str(&config_str)?; + Ok(config) +} + +fn get_jpeg_files(directory: &str) -> Vec { + let mut images = Vec::new(); + if let Ok(entries) = fs::read_dir(directory) { + for entry in entries.flatten() { + let path = entry.path(); + if let Some(ext) = path.extension() { + if ext.eq_ignore_ascii_case("jpg") || ext.eq_ignore_ascii_case("jpeg") { + images.push(path.to_string_lossy().to_string()); + } + } + } + } + images +} + +fn upload_images_batch(client: &Client, config: &Config, images: &[String], batch_num: usize, total_batches: usize) -> Result<(), Box> { + let url = format!("{}/api/v1/media", config.pixelfed_url); + let mut media_ids = Vec::new(); + + for image_path in images { + let form = reqwest::blocking::multipart::Form::new() + .file("file", image_path)?; + + let res = client.post(&url) + .bearer_auth(&config.access_token) + .multipart(form) + .send()?; + + let json: serde_json::Value = res.json()?; + if let Some(id) = json["id"].as_str() { + media_ids.push(id.to_string()); + } + } + + if !media_ids.is_empty() { + let post_url = format!("{}/api/v1/statuses", config.pixelfed_url); + let post_text = format!("{} (Batch {} out of {})", config.default_text, batch_num, total_batches); + let body = serde_json::json!({ + "status": post_text, + "media_ids": media_ids, + "visibility": config.visibility, + }); + + client.post(&post_url) + .bearer_auth(&config.access_token) + .json(&body) + .send()?; + } + + Ok(()) +} + +fn main() -> Result<(), Box> { + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let config = load_config()?; + let images = get_jpeg_files(&args[1]); + let client = Client::new(); + let total_batches = (images.len() + 19) / 20; + + for (i, chunk) in images.chunks(20).enumerate() { + upload_images_batch(&client, &config, chunk, i + 1, total_batches)?; + } + + println!("All images uploaded successfully."); + Ok(()) +}