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 / delete | Channel::send · reply · edit · delete |
| React (emoji or custom image) | Channel::react · react_custom |
| Send & receive files | Channel::send_file · VectorBot::download_attachment / save_attachment |
| Receive messages | VectorBot::on_message |
Answer typed slash commands (with a / picker) | VectorBot::command → CommandBuilder |
| Receive everything (joins, reactions, invites…) | VectorBot::on_event → match on BotEvent |
| Moderate a community | IncomingMessage::member → Member::kick · ban · grant_admin |
| Manage a community | IncomingMessage::community / VectorBot::community → Community |
| Be invitable safely | builder().public() / whitelist(..) |
| Manage profiles | fetch_profile · update_profile · block … |
| Anything else | bot.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 accountsAuto-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/commandrouter (/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_botRe-exports§
pub use vector_core;
Modules§
- nostr
- Re-exported Nostr primitives, so downstreams can depend only on
vector_sdk.
Structs§
- Attachment
- Attachment
File - 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, orIncomingMessage::channel. - Command
Builder - Builder returned by
VectorBot::command; finish withrun. - Command
Ctx - 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, orIncomingMessage::community. To message a channel within it, use aChannel(bot.channel(channel_id)). - Core
Config - Configuration for initializing VectorCore.
- Delete
Outcome - Outcome of a delete-own-* operation.
- Edit
Entry - Image
Metadata - Incoming
Message - An inbound message delivered to an
VectorBot::on_messagehandler. The same handler receives both DMs and Community channel messages — usereply/channelto respond uniformly without caring which it is. - Login
Result - Member
- A handle to a member of a community — act on them directly. Obtained from
Community::memberorIncomingMessage::member. - Message
- NoOp
Event Handler - No-op handler for CLI/tests.
- Reaction
- Send
Result - Result of sending a message.
- Serializable
Chat - Site
Metadata - Slim
Profile - Profile with npub string instead of interner handle. Used for:
- Status
- Vector
Bot - A logged-in Vector bot: an identity connected to relays, ready to send and
receive. Cheap to
Clone— clones share the same underlying session. - Vector
BotBuilder - Builder for a
VectorBot. Created viaVectorBot::builder. - Vector
Core - 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_idis the sender’s npub for a DM, the channel id for a Community message. - Channel
Kind - Whether a
Channeltargets a direct message or a Community channel. - Error
- Alias for the SDK’s error type. Unified error type for all vector-core operations.
- Invite
Policy - How a bot handles inbound Community invites (gift-wrapped invite bundles). Set on the builder
with
public/whitelist/invite_policy. - Sync
Priority - Priority levels for profile syncing.
- Vector
Error - 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§
- Event
Emitter - Emits events to the UI layer (Tauri frontend, CLI output, SDK callbacks).
- Inbound
Event Handler - Platform-specific callbacks for inbound event processing.
Type Aliases§
- Result
- Convenience alias used throughout vector-core (matches src-tauri’s
Result<T, String>pattern).