A better Rust ATProto crate
1use clap::Parser; 2use jacquard::CowStr; 3use jacquard::api::app_bsky::actor::profile::Profile; 4use jacquard::client::{Agent, AgentSessionExt, FileAuthStore}; 5use jacquard::oauth::client::OAuthClient; 6use jacquard::oauth::loopback::LoopbackConfig; 7use jacquard::types::string::AtUri; 8 9#[derive(Parser, Debug)] 10#[command(author, version, about = "Update profile display name and description")] 11struct Args { 12 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 13 input: CowStr<'static>, 14 15 /// New display name 16 #[arg(long)] 17 display_name: Option<String>, 18 19 /// New bio/description 20 #[arg(long)] 21 description: Option<String>, 22 23 /// Path to auth store file (will be created if missing) 24 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 25 store: String, 26} 27 28#[tokio::main] 29async fn main() -> miette::Result<()> { 30 let args = Args::parse(); 31 32 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store)); 33 let session = oauth 34 .login_with_local_server(args.input, Default::default(), LoopbackConfig::default()) 35 .await?; 36 37 let agent: Agent<_> = Agent::from(session); 38 39 // Get session info to build the at:// URI for the profile record 40 let (did, _) = agent 41 .info() 42 .await 43 .ok_or_else(|| miette::miette!("No session info available"))?; 44 45 // Profile records use "self" as the rkey 46 let uri_string = format!("at://{}/app.bsky.actor.profile/self", did); 47 let uri = AtUri::new(&uri_string)?; 48 49 // Update profile in-place using the fetch-modify-put pattern 50 agent 51 .update_record::<Profile>(uri, |profile| { 52 if let Some(name) = &args.display_name { 53 profile.display_name = Some(CowStr::from(name.clone())); 54 } 55 if let Some(desc) = &args.description { 56 profile.description = Some(CowStr::from(desc.clone())); 57 } 58 }) 59 .await?; 60 61 println!("✓ Profile updated successfully"); 62 63 Ok(()) 64}