15 releases

new 0.1.14 Dec 10, 2024
0.1.13 Nov 19, 2024
0.1.12 Oct 28, 2024
0.1.11 Sep 20, 2024
0.1.3 Jun 26, 2024

#144 in Web programming

Download history 15/week @ 2024-08-19 22/week @ 2024-08-26 177/week @ 2024-09-02 182/week @ 2024-09-09 362/week @ 2024-09-16 62/week @ 2024-09-23 59/week @ 2024-09-30 22/week @ 2024-10-07 39/week @ 2024-10-14 15/week @ 2024-10-21 179/week @ 2024-10-28 66/week @ 2024-11-04 166/week @ 2024-11-11 340/week @ 2024-11-18 109/week @ 2024-11-25 116/week @ 2024-12-02

735 downloads per month
Used in 3 crates

MIT license

1MB
24K SLoC

Bsky SDK: ATrium-based SDK for Bluesky.

Rust

  • ✔️ APIs for ATProto and Bluesky.
  • ✔️ Session management (same as atrium-api's AtpAgent).
  • ✔️ Moderation APIs.
  • ✔️ A RichText library.

Usage

Session management

Log into a server using these APIs. You'll need an active session for most methods.

use bsky_sdk::BskyAgent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = BskyAgent::builder().build().await?;
    let session = agent.login("alice@mail.com", "hunter2").await?;
    Ok(())
}

You can save the agent config (including the session) to a file and load it later:

use bsky_sdk::agent::config::FileStore;
use bsky_sdk::BskyAgent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = BskyAgent::builder().build().await?;
    agent.login("...", "...").await?;
    agent
        .to_config()
        .await
        .save(&FileStore::new("config.json"))
        .await?;
    Ok(())
}
use bsky_sdk::agent::config::{Config, FileStore};
use bsky_sdk::BskyAgent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = BskyAgent::builder()
        .config(Config::load(&FileStore::new("config.json")).await?)
        .build()
        .await?;
    let result = agent.api.com.atproto.server.get_session().await;
    assert!(result.is_ok());
    Ok(())
}

Moderation

The moderation APIs have almost the same functionality as the official SDK (@atproto/api).

use bsky_sdk::moderation::decision::DecisionContext;
use bsky_sdk::BskyAgent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = BskyAgent::builder().build().await?;
    // log in...

    // First get the user's moderation prefs and their label definitions
    let preferences = agent.get_preferences(true).await?;
    let moderator = agent.moderator(&preferences).await?;

    // in feeds
    let output = agent
        .api
        .app
        .bsky
        .feed
        .get_timeline(
            atrium_api::app::bsky::feed::get_timeline::ParametersData {
                algorithm: None,
                cursor: None,
                limit: None,
            }
            .into(),
        )
        .await?;
    for feed_view_post in &output.feed {
        // We call the appropriate moderation function for the content
        let post_mod = moderator.moderate_post(&feed_view_post.post);
        // don't include in feeds?
        println!(
            "{:?} (filter: {})",
            feed_view_post.post.cid.as_ref(),
            post_mod.ui(DecisionContext::ContentList).filter()
        );
    }
    Ok(())
}

Posts

Create a Bluesky post:

use atrium_api::types::string::Datetime;
use bsky_sdk::BskyAgent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let agent = BskyAgent::builder().build().await?;
    agent.login("...", "...").await?;

    agent
        .create_record(atrium_api::app::bsky::feed::post::RecordData {
            created_at: Datetime::now(),
            embed: None,
            entities: None,
            facets: None,
            labels: None,
            langs: None,
            reply: None,
            tags: None,
            text: "Hello world, from Rust!".to_string(),
        })
        .await?;
    Ok(())
}

RichText

Creating a RichText object from a string:

use bsky_sdk::rich_text::RichText;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let rt = RichText::new_with_detect_facets(
        "Hello @alice.com, check out this link: https://example.com",
    )
    .await?;

    let segments = rt.segments();
    assert_eq!(segments.len(), 4);
    assert!(segments[0].text == "Hello ");
    assert!(segments[1].text == "@alice.com" && segments[1].mention().is_some());
    assert!(segments[2].text == ", check out this link: ");
    assert!(segments[3].text == "https://example.com" && segments[3].link().is_some());

    let record_data = atrium_api::app::bsky::feed::post::RecordData {
        created_at: atrium_api::types::string::Datetime::now(),
        embed: None,
        entities: None,
        facets: rt.facets,
        labels: None,
        langs: None,
        reply: None,
        tags: None,
        text: rt.text,
    };
    println!("{:?}", record_data);
    Ok(())
}

Calculating string lengths:

use bsky_sdk::rich_text::RichText;

fn main() {
    let rt = RichText::new("👨‍👩‍👧‍👧", None);
    assert_eq!(rt.text.len(), 25);
    assert_eq!(rt.grapheme_len(), 1);
}

Dependencies

~8–21MB
~298K SLoC