A better Rust ATProto crate
1[![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) 2 3# Jacquard 4 5A suite of Rust crates intended to make it much easier to get started with atproto development, without sacrificing flexibility or performance. 6 7[Jacquard is simpler](https://whtwnd.com/nonbinary.computer/3m33efvsylz2s) because it is designed in a way which makes things simple that almost every other atproto library seems to make difficult. 8 9It is also designed around zero-copy/borrowed deserialization: types like [`Post<'_>`](https://tangled.org/@nonbinary.computer/jacquard/blob/main/crates/jacquard-api/src/app_bsky/feed/post.rs) can borrow data (via the [`CowStr<'_>`](https://docs.rs/jacquard/latest/jacquard/cowstr/enum.CowStr.html) type and a host of other types built on top of it) directly from the response buffer instead of allocating owned copies. Owned versions are themselves mostly inlined or reference-counted pointers and are therefore still quite efficient. The `IntoStatic` trait (which is derivable) makes it easy to get an owned version and avoid worrying about lifetimes. 10 11## 0.7.0 Release Highlights: 12 13- **Bluesky-style rich text support** 14 - Parses from supplied text as well as explicit builder 15 - Sanitizes input text 16 - Also handles \[]() Markdown-style links 17 - Optionally pulls out candidates for link/record embedding 18 - Optionally fetches Opengraph link data for external links 19- **Moderation label application** 20 - Generic implementation of atproto moderation/labeling client-side filtering/tagging via traits 21 - Implementations for Bluesky and other types on best-effort basis 22 - Demonstration options for use while avoiding Bluesky namespace or AppView infrastructure 23- **Websocket Subscriber-sent message control traits/code** 24 - Primarily useful for Jetstream or other custom Websocket services 25- Fixed some `Data` value type deserialization issues 26 27> [!WARNING] 28> A lot of the streaming code is still pretty experimental. The examples work, though.\ 29The modules are also less well-documented, and don't have code examples. There are also a lot of utility functions for conveniently working with the streams and transforming them which are lacking. Use [`n0-future`](https://docs.rs/n0-future/latest/n0_future/index.html) to work with them, that is what Jacquard uses internally as much as possible. 30 31### Changelog 32 33[CHANGELOG.md](./CHANGELOG.md) 34 35## Goals and Features 36 37- Validated, spec-compliant, easy to work with, and performant baseline types 38- Batteries-included, but easily replaceable batteries. 39 - Easy to extend with custom lexicons using code generation or handwritten api types 40 - Straightforward OAuth 41 - Stateless options (or options where you handle the state) for rolling your own 42 - All the building blocks of the convenient abstractions are available 43 - Server-side convenience features 44- Lexicon Data value type for working with unknown atproto data (dag-cbor or json) 45- An order of magnitude less boilerplate than some existing crates 46- Use as much or as little from the crates as you need 47 48## Example 49 50Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline. 51 52```rust 53// Note: this requires the `loopback` feature enabled (it is currently by default) 54use clap::Parser; 55use jacquard::CowStr; 56use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 57use jacquard::client::{Agent, FileAuthStore}; 58use jacquard::oauth::client::OAuthClient; 59use jacquard::oauth::loopback::LoopbackConfig; 60use jacquard::types::xrpc::XrpcClient; 61use miette::IntoDiagnostic; 62 63#[derive(Parser, Debug)] 64#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] 65struct Args { 66 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 67 input: CowStr<'static>, 68 69 /// Path to auth store file (will be created if missing) 70 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 71 store: String, 72} 73 74#[tokio::main] 75async fn main() -> miette::Result<()> { 76 let args = Args::parse(); 77 78 // Build an OAuth client with file-backed auth store and default localhost config 79 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store)); 80 // Authenticate with a PDS, using a loopback server to handle the callback flow 81 let session = oauth 82 .login_with_local_server( 83 args.input.clone(), 84 Default::default(), 85 LoopbackConfig::default(), 86 ) 87 .await?; 88 // Wrap in Agent and fetch the timeline 89 let agent: Agent<_> = Agent::from(session); 90 let timeline = agent 91 .send(&GetTimeline::new().limit(5).build()) 92 .await? 93 .into_output()?; 94 for (i, post) in timeline.feed.iter().enumerate() { 95 println!("\n{}. by {}", i + 1, post.post.author.handle); 96 println!( 97 " {}", 98 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 99 ); 100 } 101 102 Ok(()) 103} 104 105``` 106 107If you have `just` installed, you can run the [examples](https://tangled.org/@nonbinary.computer/jacquard/tree/main/examples) using `just example {example-name} {ARGS}` or `just examples` to see what's available. 108 109## Component crates 110 111Jacquard is broken up into several crates for modularity. The correct one to use is generally `jacquard` itself, as it re-exports most of the others. 112 113| | | | 114| --- | --- | --- | 115| `jacquard` | Main crate | [![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) | 116|`jacquard-common` | Foundation crate | [![Crates.io](https://img.shields.io/crates/v/jacquard-common.svg)](https://crates.io/crates/jacquard-common) [![Documentation](https://docs.rs/jacquard-common/badge.svg)](https://docs.rs/jacquard-common)| 117| `jacquard-axum` | Axum extractor and other helpers | [![Crates.io](https://img.shields.io/crates/v/jacquard-axum.svg)](https://crates.io/crates/jacquard-axum) [![Documentation](https://docs.rs/jacquard-axum/badge.svg)](https://docs.rs/jacquard-axum) | 118| `jacquard-api` | Autogenerated API bindings | [![Crates.io](https://img.shields.io/crates/v/jacquard-api.svg)](https://crates.io/crates/jacquard-api) [![Documentation](https://docs.rs/jacquard-api/badge.svg)](https://docs.rs/jacquard-api) | 119| `jacquard-oauth` | atproto OAuth implementation | [![Crates.io](https://img.shields.io/crates/v/jacquard-oauth.svg)](https://crates.io/crates/jacquard-oauth) [![Documentation](https://docs.rs/jacquard-oauth/badge.svg)](https://docs.rs/jacquard-oauth) | 120| `jacquard-identity` | Identity resolution | [![Crates.io](https://img.shields.io/crates/v/jacquard-identity.svg)](https://crates.io/crates/jacquard-identity) [![Documentation](https://docs.rs/jacquard-identity/badge.svg)](https://docs.rs/jacquard-identity) | 121| `jacquard-lexicon` | Lexicon parsing and code generation | [![Crates.io](https://img.shields.io/crates/v/jacquard-lexicon.svg)](https://crates.io/crates/jacquard-lexicon) [![Documentation](https://docs.rs/jacquard-lexicon/badge.svg)](https://docs.rs/jacquard-lexicon) | 122| `jacquard-derive` | Macros for lexicon types | [![Crates.io](https://img.shields.io/crates/v/jacquard-derive.svg)](https://crates.io/crates/jacquard-derive) [![Documentation](https://docs.rs/jacquard-derive/badge.svg)](https://docs.rs/jacquard-derive) | 123 124## Development 125 126This repo uses [Flakes](https://nixos.asia/en/flakes) 127 128```bash 129# Dev shell 130nix develop 131 132# or run via cargo 133nix develop -c cargo run 134 135# build 136nix build 137``` 138 139There's also a [`justfile`](https://just.systems/) for Makefile-esque commands to be run inside of the devShell, and you can generally `cargo ...` or `just ...` whatever just fine if you don't want to use Nix and have the prerequisites installed. 140 141[![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE)