A better Rust ATProto crate
40 4 0

Clone this repository

https://tangled.org/zbrox.com/jacquard
git@knot.attic.zbrox.com:zbrox.com/jacquard

For self-hosted knots, clone URLs may differ based on your setup.

README.md

Jacquard#

A suite of Rust crates for the AT Protocol.

Crates.io Documentation License

Goals#

  • Validated, spec-compliant, easy to work with, and performant baseline types (including typed at:// uris)
  • Batteries-included, but easily replaceable batteries.
    • Easy to extend with custom lexicons
  • lexicon Value type for working with unknown atproto data (dag-cbor or json)
  • order of magnitude less boilerplate than some existing crates
    • either the codegen produces code that's easy to work with, or there are good handwritten wrappers
  • didDoc type with helper methods for getting handles, multikey, and PDS endpoint
  • use as much or as little from the crates as you need

Example#

Dead simple api client. Logs in, prints the latest 5 posts from your timeline.

use clap::Parser;
use jacquard::CowStr;
use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
use jacquard::api::com_atproto::server::create_session::CreateSession;
use jacquard::client::{AuthenticatedClient, Session, XrpcClient};
use miette::IntoDiagnostic;

#[derive(Parser, Debug)]
#[command(author, version, about = "Jacquard - AT Protocol client demo")]
struct Args {
    /// Username/handle (e.g., alice.mosphere.at)
    #[arg(short, long)]
    username: CowStr<'static>,

    /// PDS URL (e.g., https://bsky.social)
    #[arg(long, default_value = "https://bsky.social")]
    pds: CowStr<'static>,

    /// App password
    #[arg(short, long)]
    password: CowStr<'static>,
}

#[tokio::main]
async fn main() -> miette::Result<()> {
    let args = Args::parse();

    // Create HTTP client
    let mut client = AuthenticatedClient::new(reqwest::Client::new(), args.pds);

    // Create session
    let session = Session::from(
        client
            .send(
                CreateSession::new()
                    .identifier(args.username)
                    .password(args.password)
                    .build(),
            )
            .await?
            .into_output()?,
    );

    println!("logged in as {} ({})", session.handle, session.did);
    client.set_session(session);

    // Fetch timeline
    println!("\nfetching timeline...");
    let timeline = client
        .send(GetTimeline::new().limit(5).build())
        .await?
        .into_output()?;

    println!("\ntimeline ({} posts):", timeline.feed.len());
    for (i, post) in timeline.feed.iter().enumerate() {
        println!("\n{}. by {}", i + 1, post.post.author.handle);
        println!(
            "   {}",
            serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
        );
    }

    Ok(())
}

Development#

This repo uses Flakes from the get-go.

# Dev shell
nix develop

# or run via cargo
nix develop -c cargo run

# build
nix build

There's also a justfile 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.