A better Rust ATProto crate
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 90 lines 2.7 kB view raw
1use clap::Parser; 2use jacquard::CowStr; 3use jacquard::api::app_bsky::embed::images::{Image, Images}; 4use jacquard::api::app_bsky::feed::post::{Post, PostEmbed}; 5use jacquard::client::{Agent, AgentSessionExt, FileAuthStore}; 6use jacquard::oauth::client::OAuthClient; 7use jacquard::oauth::loopback::LoopbackConfig; 8use jacquard::types::blob::MimeType; 9use jacquard::types::string::Datetime; 10use miette::IntoDiagnostic; 11use smol_str::SmolStr; 12use std::path::PathBuf; 13 14#[derive(Parser, Debug)] 15#[command(author, version, about = "Create a post with an image")] 16struct Args { 17 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 18 input: CowStr<'static>, 19 20 /// Post text 21 #[arg(short, long)] 22 text: String, 23 24 /// Path to image file 25 #[arg(short, long)] 26 image: PathBuf, 27 28 /// Alt text for image 29 #[arg(long)] 30 alt: Option<String>, 31 32 /// Path to auth store file (will be created if missing) 33 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 34 store: String, 35} 36 37#[tokio::main] 38async fn main() -> miette::Result<()> { 39 let args = Args::parse(); 40 41 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store)); 42 let session = oauth 43 .login_with_local_server(args.input, Default::default(), LoopbackConfig::default()) 44 .await?; 45 46 let agent: Agent<_> = Agent::from(session); 47 48 // Read image file 49 let image_data = std::fs::read(&args.image).into_diagnostic()?; 50 51 // Infer mime type from extension 52 let mime_str = match args.image.extension().and_then(|s| s.to_str()) { 53 Some("jpg") | Some("jpeg") => "image/jpeg", 54 Some("png") => "image/png", 55 Some("gif") => "image/gif", 56 Some("webp") => "image/webp", 57 _ => "image/jpeg", // default 58 }; 59 let mime_type = MimeType::new(mime_str); 60 61 println!("Uploading image..."); 62 let blob = agent.upload_blob(image_data, mime_type).await?; 63 64 // Create post with image embed 65 let post = Post { 66 text: SmolStr::from(args.text), 67 created_at: Datetime::now(), 68 embed: Some(PostEmbed::Images(Box::new(Images { 69 images: vec![Image { 70 alt: SmolStr::from(args.alt.unwrap_or_default()), 71 image: blob.into(), 72 aspect_ratio: None, 73 extra_data: Default::default(), 74 }], 75 extra_data: Default::default(), 76 }))), 77 entities: None, 78 facets: None, 79 labels: None, 80 langs: None, 81 reply: None, 82 tags: None, 83 extra_data: Default::default(), 84 }; 85 86 let output = agent.create_record(post, None).await?; 87 println!("Created post with image: {}", output.uri); 88 89 Ok(()) 90}