> For the complete documentation index, see [llms.txt](https://vector-privacy.gitbook.io/vector-privacy/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://vector-privacy.gitbook.io/vector-privacy/vector-sdk/readme.md).

# Vector SDK

Vector SDK is an ergonomic Rust library for building bots on Vector, a private, encrypted messenger built on Nostr. It is a thin, friendly layer over the [`vector-core`](https://crates.io/crates/vector-core) engine, the same engine that powers the Vector desktop and mobile apps. A working bot can be built in about a dozen lines of Rust, without ever touching the underlying protocol or encryption.

> **Version compatibility:** This SDK (`v0.3.0`) is built against Vector `v0.4.0` and later. The SDK follows its own [semantic versioning](https://semver.org/) independent of the main Vector app, so its version number will not always match the app's release version.

## Features

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-robot">:robot:</i></h4></td><td><strong>Vector Bots</strong></td><td>Custom Metadata</td><td><a href="/files/7XsNFQ1ZJ2HlTZ9k6KrQ">/files/7XsNFQ1ZJ2HlTZ9k6KrQ</a></td><td></td><td></td></tr><tr><td><h4><i class="fa-people-group">:people-group:</i></h4></td><td><strong>Communities</strong></td><td>Roles, Modes, Invites</td><td><a href="/files/ygSHj3ZpBUyfbT1EqRCG">/files/ygSHj3ZpBUyfbT1EqRCG</a></td><td></td><td></td></tr><tr><td><h4><i class="fa-envelope-open-text">:envelope-open-text:</i></h4></td><td><strong>Send/Receive Files</strong></td><td>Private Messages &#x26; Files</td><td><a href="/files/iPYlZKAFODyyg31M4uFZ">/files/iPYlZKAFODyyg31M4uFZ</a></td><td></td><td></td></tr><tr><td><h4><i class="fa-plug">:plug:</i></h4></td><td><strong>Auto-Reconnect</strong></td><td>Rides Out Drops</td><td><a href="/files/ru45H2KtSyhICRBMelbo">/files/ru45H2KtSyhICRBMelbo</a></td><td></td><td></td></tr><tr><td><h4><i class="fa-onion">:onion:</i></h4></td><td><strong>Embedded Tor</strong></td><td>One Builder Call</td><td><a href="/files/g5Jq885MDfM8LCsV3xFV">/files/g5Jq885MDfM8LCsV3xFV</a></td><td></td><td></td></tr><tr><td><h4><i class="fa-key-skeleton">:key-skeleton:</i></h4></td><td><strong>Keyless Identity</strong></td><td>Zero Setup</td><td><a href="/files/ElRAdDtTiTfoFcT9VPWZ">/files/ElRAdDtTiTfoFcT9VPWZ</a></td><td></td><td></td></tr></tbody></table>

### Quick Start

```toml
[dependencies]
vector_sdk = "0.3"
tokio = { version = "1", features = ["full"] }
```

```rust
use vector_sdk::VectorBot;

#[tokio::main]
async fn main() -> vector_sdk::Result<()> {
    let bot = VectorBot::builder()
        .nsec("nsec1...")          // the bot's key — or omit it and one is created for you
        .build()
        .await?;

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

    // Reply to every message the bot receives.
    bot.on_message(|_bot, msg| async move {
        if msg.is_mine() { return; }
        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 anything it missed while offline.

***

### One API, Everywhere

Your bot sends and receives through a `Channel`, a direct-message chat or a community channel, handled identically. You never need to branch on "is this a DM or a community?", you just send and reply.

```rust
// `msg` could be from a DM or a community channel — same code either way:
msg.reply("got it").await?;        // reply in the same chat or channel
msg.react("👍").await?;            // react to it
msg.channel().typing().await?;     // show a typing indicator

// Or message a chat or channel directly by its id:
bot.channel(id).send("hi").await?;
```

***

## What Your Bot Can Do

### Messaging

Available on any chat or channel via `bot.channel(id)`, `bot.dm(npub)`, or `msg.channel()`:

| Method                                                              | What it does                           |
| ------------------------------------------------------------------- | -------------------------------------- |
| `channel.send(text)`                                                | Send a message                         |
| `channel.reply(msg_id, text)`                                       | Reply to a specific message (threaded) |
| `channel.react(msg_id, "👍")`                                       | React with an emoji                    |
| `channel.react_custom(msg_id, ":code:", url)`                       | React with a custom image emoji        |
| `channel.typing()`                                                  | Show a typing indicator                |
| `channel.edit(msg_id, text)` / `channel.delete(msg_id)`             | Edit / delete a message the bot sent   |
| `channel.send_file(path)`                                           | Send a file                            |
| `bot.download_attachment(&att)` / `bot.save_attachment(&att, path)` | Get a received file                    |

On an incoming message, `msg.reply(text)` answers it, `msg.react(emoji)` reacts to it, and any received files are available on `msg.message.attachments`.

### Communities

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

```rust
if let Some(member) = msg.member() {
    member.kick().await?;          // or .ban() / .unban()
    member.grant_admin().await?;   // or .revoke_admin()
    if member.is_admin() { /* ... */ }
}

// Or manage a community directly:
let community = bot.community(community_id);  // also: msg.community(), bot.communities()
community.invite("npub1...").await?;
let link = community.create_invite().await?;
for m in community.members().await { /* ... */ }
```

A `Community` also supports `edit(name, description)` to update its metadata, `leave()`, and `dissolve()` (owner-only, permanently ends the community). `capabilities()` and `roles()` expose the underlying role-permission data for more advanced moderation logic.

### Joining Communities

To be useful in a community, a bot has to accept invites. Choose how:

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

By default, invites wait for you to handle them manually (`bot.pending_invites()` / `bot.accept_invite(id)`). Auto-accept also picks up invites that arrived while the bot was offline, so a restarted bot still joins what it was invited to.

### Receiving

`bot.on_message(handler)` runs your handler for every incoming message, DM or community alike. A slow handler will not hold up the others.

For more than messages, `bot.on_event(|bot, event|)` gives you the full event stream as a `BotEvent`, match the parts you care about:

```rust
bot.on_event(|bot, event| async move {
    match event {
        BotEvent::Message(msg) if !msg.is_mine() => { msg.reply("hi").await.ok(); }
        BotEvent::MemberJoin { channel_id, npub } => {
            bot.channel(channel_id).send("welcome!").await.ok();
        }
        BotEvent::MessageUpdate { .. } => { /* a reaction or edit landed */ }
        _ => {}
    }
}).await?;
```

`BotEvent` covers messages, reactions and edits, deletes, members joining or leaving, typing, invites, and the bot being removed from a community.

### Staying Connected

If the bot loses its connection, it reconnects on its own and catches up on what it missed. Your handler fires for messages that arrive while the bot is running; to read older history, use `bot.core().get_messages(...)`.

### Profiles

`bot.fetch_profile(npub)`, `bot.cached_profile(npub)`, `bot.update_profile(...)`, `bot.set_status(...)`, `bot.block(npub)` / `bot.unblock(npub)`, `bot.blocked_users()`, `bot.set_nickname(...)`, and `bot.upload_image(path)` for setting an avatar. Bots are automatically tagged as bots when their profile is updated.

### Going Deeper

`bot.core()` exposes the full `VectorCore` engine for anything not surfaced directly on the SDK, including creating communities, reading message history, and lower-level controls.

***

### Examples

Runnable, self-contained bots live in `examples/`, each one demonstrating a single concept. Every example needs `VECTOR_NSEC` (the bot's key); a few take additional environment variables.

| Example             | What it shows                                                         |
| ------------------- | --------------------------------------------------------------------- |
| `echo_bot`          | The minimal hello-world, replies to every message.                    |
| `slash_command_bot` | A `/command` router: `/ping`, `/echo`, `/roll`, `/help`.              |
| `ai_bot`            | An LLM chatbot: typing indicator, threaded replies, per-chat history. |
| `moderation_bot`    | Welcomes new members and auto-bans on a word filter.                  |
| `whitelist_bot`     | A private bot that only joins communities it trusts.                  |
| `file_bot`          | Sends one file, then exits.                                           |
| `save_files_bot`    | Saves every received file to disk.                                    |

```bash
# Echo bot — replies to every message
VECTOR_NSEC=nsec1... cargo run --example echo_bot

# AI bot — wire any OpenAI-compatible endpoint to your chats
OPENAI_API_KEY=sk-... VECTOR_NSEC=nsec1... cargo run --example ai_bot
```

***

### Accounts & Keys

* **No key.** `build()` creates an identity on first run and reuses it on every run after, the simplest way to get a bot online. Running several keyless bots? Give each its own `.data_dir(...)` so they get distinct identities.
* **`.nsec("nsec1...")`** — use an existing key.
* **`.mnemonic("twelve words ...")`** — use a 12-word seed phrase.
* **`.password("...")`** — only needed for keys that are encrypted at rest.
* **`VectorBot::generate_nsec()`** — mint a fresh key yourself, outside the builder.

A keyless bot's identity is stable across restarts, so it keeps its chats and community memberships. Storage defaults to a per-OS application directory; override it with `.data_dir(path)`.

***

### One Bot per Process

A bot owns the process while it runs, so run one bot per process. To run several bots, run several processes.

***

### Tor (Optional)

To route the bot through Tor, enable the `tor` feature and call `.tor()` on the builder. The feature alone only compiles Tor in; `.tor()` is what actually turns it on:

```toml
vector_sdk = { version = "0.3", features = ["tor"] }
```

```rust
let bot = VectorBot::builder().nsec(key).tor().build().await?;
// .tor_bridges(["1.2.3.4:443 <fingerprint>"]) for networks where Tor is blocked
```

Tor is bootstrapped during `build()` before the bot connects, so it never touches the network in the clear. The feature is off by default, keeping the dependency tree light for bots that don't need it.

***

## License

This project is licensed under the MIT License. See the [LICENSE](https://github.com/VectorPrivacy/Vector-SDK/blob/mls-groups/LICENSE) file for details.
