A better Rust ATProto crate
0
fork

Configure Feed

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

at main 88 lines 3.1 kB view raw
1use clap::Parser; 2use jacquard::CowStr; 3use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 4use jacquard::client::{Agent, FileAuthStore}; 5use jacquard::oauth::atproto::AtprotoClientMetadata; 6use jacquard::oauth::client::OAuthClient; 7#[cfg(feature = "loopback")] 8use jacquard::oauth::loopback::LoopbackConfig; 9use jacquard::xrpc::XrpcClient; 10#[cfg(not(feature = "loopback"))] 11use jacquard_oauth::types::AuthorizeOptions; 12use miette::IntoDiagnostic; 13use smol_str::SmolStr; 14 15#[derive(Parser, Debug)] 16#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] 17struct Args { 18 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 19 input: CowStr<'static>, 20 21 /// Path to auth store file (will be created if missing) 22 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 23 store: String, 24} 25 26#[tokio::main] 27async fn main() -> miette::Result<()> { 28 let args = Args::parse(); 29 30 // File-backed auth store shared by OAuthClient and session registry 31 let store = FileAuthStore::new(&args.store); 32 let client_data = jacquard_oauth::session::ClientData { 33 keyset: None, 34 // Default sets normal localhost redirect URIs and "atproto transition:generic" scopes. 35 // The localhost helper will ensure you have at least "atproto" and will fix urls 36 config: AtprotoClientMetadata::default_localhost(), 37 }; 38 39 // Build an OAuth client (this is reusable, and can create multiple sessions) 40 let oauth = OAuthClient::new(store, client_data); 41 42 #[cfg(feature = "loopback")] 43 // Authenticate with a PDS, using a loopback server to handle the callback flow 44 let session = oauth 45 .login_with_local_server( 46 args.input.clone(), 47 Default::default(), 48 LoopbackConfig::default(), 49 ) 50 .await?; 51 52 #[cfg(not(feature = "loopback"))] 53 let session = { 54 use std::io::{BufRead, Write, stdin, stdout}; 55 56 let auth_url = oauth 57 .start_auth(args.input, AuthorizeOptions::default()) 58 .await?; 59 60 println!("To authenticate with your PDS, visit:\n{}\n", auth_url); 61 print!("\nPaste the callback url here:"); 62 stdout().lock().flush().into_diagnostic()?; 63 let mut url = String::new(); 64 stdin().lock().read_line(&mut url).into_diagnostic()?; 65 66 let uri = url.trim().parse::<http::Uri>().into_diagnostic()?; 67 let params = 68 serde_html_form::from_str(uri.query().ok_or(miette::miette!("invalid callback url"))?) 69 .into_diagnostic()?; 70 oauth.callback(params).await? 71 }; 72 73 // Wrap in Agent and fetch the timeline 74 let agent: Agent<_> = Agent::from(session); 75 let output = agent 76 .send(GetTimeline::<SmolStr>::new().limit(5).build()) 77 .await?; 78 let timeline = output.into_output()?; 79 for (i, post) in timeline.feed.iter().enumerate() { 80 println!("\n{}. by {}", i + 1, post.post.author.handle); 81 println!( 82 " {}", 83 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 84 ); 85 } 86 87 Ok(()) 88}