freeq SDK

Rust crate for building IRC clients, bots, and integrations that authenticate with AT Protocol identities.

# Cargo.toml
[dependencies]
freeq-sdk = { git = "https://github.com/chad/freeq" }

Architecture

The SDK exposes a (ClientHandle, Receiver<Event>) pattern. Connect, authenticate, then consume events in a loop:

use freeq_sdk::client::{ClientHandle, Config};
use freeq_sdk::event::Event;

let config = Config {
    server: "irc.freeq.at".into(),
    port: 6697,
    tls: true,
    nick: "myapp".into(),
    ..Default::default()
};

let (handle, mut events) = freeq_sdk::client::connect(config).await?;

// Send commands
handle.join("#mychannel").await?;
handle.privmsg("#mychannel", "Hello from my app").await?;

// Receive events
while let Some(event) = events.recv().await {
    match event {
        Event::Privmsg { from, target, text, .. } => {
            println!("{from} -> {target}: {text}");
        }
        _ => {}
    }
}

Authentication

The SDK supports multiple authentication methods via the pluggable ChallengeSigner trait:

SignerUse Case
KeySignerSign challenges with a secp256k1 or ed25519 private key
PdsSessionSignerApp password or OAuth token — calls PDS to verify
StubSignerTesting — returns a fixed response

OAuth Flow

use freeq_sdk::oauth;

// Full browser-based OAuth flow
let session = oauth::authenticate("yourname.bsky.social").await?;
// session contains access_jwt, refresh_jwt, DPoP key, PDS URL

// Create a signer from the OAuth session
let signer = PdsSessionSigner::new_with_refresh(
    session.did, session.pds_url,
    session.access_jwt, session.refresh_jwt,
    session.dpop_key,
);

Bot Framework

High-level framework for building command-based bots with permission levels:

use freeq_sdk::bot::{Bot, BotConfig, Command, PermissionLevel};

let mut bot = Bot::new(BotConfig {
    server: "irc.freeq.at".into(),
    port: 6697, tls: true,
    nick: "mybot".into(),
    channels: vec!["#bots".into()],
    admin_dids: vec!["did:plc:yourdid".into()],
    ..Default::default()
});

// Anyone can use this
bot.command(Command::new("ping", "Check if bot is alive",
    PermissionLevel::Anyone,
    |ctx| Box::pin(async move { ctx.reply("pong!").await; Ok(()) })
));

// Only authenticated users
bot.command(Command::new("whoami", "Show your DID",
    PermissionLevel::Authenticated,
    |ctx| Box::pin(async move {
        let did = ctx.sender_did.as_deref().unwrap_or("not authenticated");
        ctx.reply(&format!("You are {did}")).await;
        Ok(())
    })
));

// Only admin DIDs
bot.command(Command::new("shutdown", "Stop the bot",
    PermissionLevel::Admin,
    |ctx| Box::pin(async move { ctx.reply("Shutting down...").await; Ok(()) })
));

bot.run().await?;

End-to-End Encryption

Channel encryption with AES-256-GCM:

use freeq_sdk::e2ee;

// Encrypt with a shared passphrase
let encrypted = e2ee::encrypt("#mychannel", "secret passphrase", "Hello!");
// → "ENC1:<nonce>:<ciphertext>"

let plaintext = e2ee::decrypt("#mychannel", "secret passphrase", &encrypted)?;
// → "Hello!"

DID-based group encryption (no shared secret):

use freeq_sdk::e2ee_did;

// Group key derived from sorted member DIDs
let members = vec!["did:plc:alice", "did:plc:bob", "did:plc:carol"];
let encrypted = e2ee_did::encrypt_group(&members, epoch, "Hello group!")?;
// → "ENC2:<epoch>:<nonce>:<ciphertext>"

Media & Rich Content

Upload media to the AT Protocol PDS and send as IRC message tags:

use freeq_sdk::media;

// Upload an image
let blob = media::upload_to_pds(&session, image_bytes, "image/jpeg").await?;

// Send as a tagged message
handle.send_media("#channel", "Check this out",
    "image/jpeg", &blob.url, Some("A cool photo")).await?;

Modules

ModuleDescription
freeq_sdk::clientIRC connection, handle, events
freeq_sdk::authSASL challenge signing
freeq_sdk::oauthAT Protocol OAuth 2.0 flow
freeq_sdk::didDID resolution (plc, web)
freeq_sdk::pdsPDS client (sessions, verification)
freeq_sdk::botBot framework with commands
freeq_sdk::e2eePassphrase-based channel encryption
freeq_sdk::e2ee_didDID-based group + DM encryption
freeq_sdk::mediaPDS media upload, link previews
freeq_sdk::p2pPeer-to-peer encrypted DMs
freeq_sdk::ircIRC message parser with tags
freeq_sdk::eventEvent types

Source & Examples

freeq-sdk on GitHub
Example bots