Skip to main content

Crate vector_sdk

Crate vector_sdk 

Source
Expand description

§Vector SDK

Build a private-messaging bot in about a dozen lines.

Vector is a private, encrypted messenger. This SDK lets your bot send and receive messages, files, and reactions, join communities, and ride out network drops — without ever touching the protocol or encryption underneath.

use vector_sdk::VectorBot;

#[tokio::main]
async fn main() -> vector_sdk::Result<()> {
    let bot = VectorBot::builder()
        .nsec("nsec1...")          // or .mnemonic("twelve words ...")
        .build()
        .await?;

    println!("Online as {}", bot.npub());

    // Reply to everything. The SAME handler serves DMs *and* Community channels.
    bot.on_message(|_bot, msg| async move {
        if msg.is_mine() { return; }              // ignore our own messages
        let _ = msg.reply(&format!("You said: {}", msg.text())).await;
    }).await?;

    Ok(())
}

That bot already handles direct messages and communities, reconnects after a network drop, and catches up on what it missed.

§One API, everywhere

Your bot sends and receives through a Channel — a direct-message chat or a community channel, handled identically. You never branch on which it is: bot.channel(id) opens either by id, and msg.reply(...) answers wherever the message came from.

let chat = bot.channel(id);            // DM or Community channel — auto-detected
chat.send("hi").await?;                //
chat.react(msg_id, "👍").await?;       // identical surface either way
chat.send_file("./photo.png").await?;  //
chat.typing().await?;                  // "typing…" indicator

§What a bot can do

You want to……you call
Send / reply / edit / deleteChannel::send · reply · edit · delete
React (emoji or custom image)Channel::react · react_custom
Send & receive filesChannel::send_file · VectorBot::download_attachment / save_attachment
Receive messagesVectorBot::on_message
Answer typed slash commands (with a / picker)VectorBot::commandCommandBuilder
Receive everything (joins, reactions, invites…)VectorBot::on_event → match on BotEvent
Moderate a communityIncomingMessage::memberMember::kick · ban · grant_admin
Manage a communityIncomingMessage::community / VectorBot::communityCommunity
Be invitable safelybuilder().public() / whitelist(..)
Manage profilesfetch_profile · update_profile · block
Anything elsebot.core() → the full VectorCore facade

§Receiving: on_message vs on_event

on_message is the fast path — one async handler per inbound message, DMs and Community channels alike; a slow handler won’t hold up the others. (For slash commands, reach for command rather than matching on msg.text() here — see Commands below.)

For everything beyond messages, on_event delivers the full stream as a BotEvent you match on — Message, MessageUpdate (a reaction/edit landed), Delete, MemberJoin, MemberLeave, Typing, Invite, and Removed (the bot was kicked/banned):

bot.on_event(|bot, event| async move {
    match event {
        BotEvent::Message(msg) if !msg.is_mine() => { let _ = msg.reply("hi").await; }
        BotEvent::MemberJoin { channel_id, npub } => {
            let _ = bot.channel(channel_id).send(&format!("welcome {}!", &npub[..12])).await;
        }
        _ => {}
    }
}).await?;

§Commands

Don’t parse msg.text() by hand. Declare a command instead: give it a name, a description, and typed arguments, and the SDK publishes a machine-readable manifest for it. Every Vector client then renders a / picker listing your command, and offers a typed field per argument — a dropdown for a choice, a member picker for a user, a number field for an int — and validates the input before the invocation is ever sent. Your handler receives the arguments already parsed and type-checked.

bot.command("weather", "Current conditions for a city")
    .string("city", "Which city", true)             // required free text
    .choice("units", "Temperature units", ["c", "f"], false) // optional dropdown
    .run(|ctx| async move {
        let city = ctx.str("city").unwrap_or_default();
        let units = ctx.str("units").unwrap_or("c");
        let _ = ctx.reply(format!("Weather in {city} in °{}…", units.to_uppercase())).await;
    });

Argument types: string, int, number, flag (bool), user (an npub), and choice. Read them back off the CommandCtx with ctx.str/int/number/flag(name). A matched command runs its handler and never reaches on_message, so commands and free-form chat coexist; the manifest publishes automatically once the bot starts listening. See the slash_command_bot example for a full bot.

§Communities

When a message comes from a community, you get the sender as a member you can act on directly:

if let Some(member) = msg.member() {     // the sender, as a Member of this community
    if !member.is_admin() {
        member.ban().await?;             // or .kick() / .unban() / .grant_admin()
    }
}

§Public vs private bots

A bot must accept invites to be useful in communities, but a private bot mustn’t be spammable into random ones. Set the policy on the builder:

VectorBot::builder().nsec("nsec1...").public().build().await?;                 // accept from anyone
VectorBot::builder().nsec("nsec1...").whitelist(["npub1owner…"]).build().await?; // only these accounts

Auto-accept fires for live invites and for ones received while the bot was offline (swept on the next connect), so a restarted bot still joins what it was invited to. The default is InvitePolicy::Manual — see pending_invites / accept_invite.

§Staying connected

If the bot loses its connection, on_message / on_event reconnect on their own and catch up on what was missed. Your handler fires for messages that arrive while the bot is running; to read older history, use bot.core().get_messages(...).

§Identity: bring your own, or let the bot make one

Supply a key with nsec / mnemonic — or supply nothing, and build creates an identity on first run and persists it (identity.nsec) in the bot’s data directory, reusing the same one every run after. So a first bot needs zero setup:

let bot = VectorBot::builder().build().await?; // first run mints + stores an nsec; reused after
println!("online as {}", bot.npub());

It never mints a fresh key per run — the identity is stable, so the bot keeps its DMs and community memberships across restarts. Running several keyless bots? Give each its own data_dir so they get distinct identities.

§Single identity per process

vector_core is built on process-global state, so one VectorBot owns the process’s identity at a time. Build one bot per process. (Multiple identities means multiple processes — or VectorCore::swap_session to switch the active account in place.)

§Reaching deeper

Everything not surfaced here — creating communities, reading history, and lower-level controls — is one hop away via VectorBot::core, which hands you the full VectorCore facade.

§Examples

Runnable bots live in examples/:

  • echo_bot — the minimal hello-world; replies to every message.
  • slash_command_bot — a /command router (/ping, /roll, /help…).
  • ai_bot — an LLM chatbot with a typing indicator and threaded replies.
  • moderation_bot — welcomes joiners and auto-bans on a word filter.
  • whitelist_bot — a private bot that only joins communities it trusts.
  • file_bot / save_files_bot — send a file / receive and decrypt one.
VECTOR_NSEC=nsec1... cargo run -p vector-sdk --example echo_bot

Re-exports§

pub use vector_core;

Modules§

nostr
Re-exported Nostr primitives, so downstreams can depend only on vector_sdk.

Structs§

Attachment
AttachmentFile
Pre-upload file data.
Channel
A unified handle for a chat or channel — a DM and a Community channel behave the same. Every method routes to the right transport under the hood, so a bot author never branches on DM-vs- channel. Obtained from VectorBot::channel / dm / community, or IncomingMessage::channel.
CommandBuilder
Builder returned by VectorBot::command; finish with run.
CommandCtx
Everything a command handler needs: the triggering message, the typed arguments, and the bot.
Community
A handle to a Community for management — members, invites, roles, metadata. Obtained from VectorBot::community, VectorBot::communities, or IncomingMessage::community. To message a channel within it, use a Channel (bot.channel(channel_id)).
CoreConfig
Configuration for initializing VectorCore.
DeleteOutcome
Outcome of a delete-own-* operation.
EditEntry
ImageMetadata
IncomingMessage
An inbound message delivered to an VectorBot::on_message handler. The same handler receives both DMs and Community channel messages — use reply / channel to respond uniformly without caring which it is.
LoginResult
Member
A handle to a member of a community — act on them directly. Obtained from Community::member or IncomingMessage::member.
Message
NoOpEventHandler
No-op handler for CLI/tests.
Reaction
SendResult
Result of sending a message.
SerializableChat
SiteMetadata
SlimProfile
Profile with npub string instead of interner handle. Used for:
Status
VectorBot
A logged-in Vector bot: an identity connected to relays, ready to send and receive. Cheap to Clone — clones share the same underlying session.
VectorBotBuilder
Builder for a VectorBot. Created via VectorBot::builder.
VectorCore
The main entry point for Vector Core.

Enums§

BotEvent
Every kind of inbound event a bot can observe. Delivered to VectorBot::on_event. DMs and Community channels are unified: chat_id is the sender’s npub for a DM, the channel id for a Community message.
ChannelKind
Whether a Channel targets a direct message or a Community channel.
Error
Alias for the SDK’s error type. Unified error type for all vector-core operations.
InvitePolicy
How a bot handles inbound Community invites (gift-wrapped invite bundles). Set on the builder with public / whitelist / invite_policy.
SyncPriority
Priority levels for profile syncing.
VectorError
Unified error type for all vector-core operations.

Constants§

DISCOVERY_RELAYS
Public relays that index addressable/replaceable events network-wide — the reliable discovery path for a bot’s manifest (and profile). One list shared with the reader side (clients query these beside a chat’s own relays), so publish reach and lookup reach can’t drift apart. Public relays that index replaceable events network-wide — the reliable discovery path for bot manifests. Read side: always queried ALONGSIDE a chat’s own relays, so a manifest resolves even when a community relay is unreachable or drops stranger events (Ditto does). Write side: the SDK publishes every bot’s manifest here for the same reason.

Traits§

EventEmitter
Emits events to the UI layer (Tauri frontend, CLI output, SDK callbacks).
InboundEventHandler
Platform-specific callbacks for inbound event processing.

Type Aliases§

Result
Convenience alias used throughout vector-core (matches src-tauri’s Result<T, String> pattern).