Skip to main content

Bot Development Guide

Build bots that can read and send messages in Chirp servers using the official @chirp-dev/chirp-sdk package.

Quick Start

1. Install the SDK

npm install @chirp-dev/chirp-sdk socket.io-client

2. Generate a Bot Token

Go to User Settings → Developer → Bot Token and click Generate Token. Save it somewhere secure — it won't be shown again.

3. Set Up Your Bot Profile

In the same Developer section, set a display name and avatar for your bot. These show up when your bot posts messages.

4. Invite Your Bot to a Server

Under Bot Servers, paste a server invite code and click Join. Your bot can only interact with servers it has joined.

Tip: You can get an invite code from any server by going to Server Settings → Invites or right-clicking a server.


Using the SDK

The @chirp-dev/chirp-sdk provides a simplified API for bot development with built-in WebSocket handling, command registration, and TypeScript support.

Basic Bot

import { ChirpClient, CommandBuilder } from '@chirp-dev/chirp-sdk';

const client = new ChirpClient({
token: process.env.CHIRP_BOT_TOKEN,
wsUrl: 'ws://localhost:3001',
commandPrefix: '/',
intents: ['messages', 'messageUpdates', 'userStatus', 'typing', 'servers'],
});

// Register a simple command
client.commands.register(
new CommandBuilder()
.setName('ping')
.setDescription('Check bot latency')
.setHandler(async (ctx) => {
const startTime = Date.now();
const msg = await ctx.reply('Pinging...');
const latency = Date.now() - startTime;
await ctx.edit(msg.id, `🏓 Pong! Latency: **${latency}ms**`);
})
);

// Start the bot
await client.login();

// Bot is now ready and listening for commands!

Command Options

client.commands.register(
new CommandBuilder()
.setName('echo')
.setDescription('Repeat your message back')
.setUsage('/echo <text>')
.setHandler(async (ctx) => {
const text = ctx.args.join(' ') || '...echo?';
await ctx.reply(`🔁 ${text}`);
})
);

Accessing Bot Data

client.commands.register({
name: 'servers',
description: 'List all servers the bot has access to',
handler: async (ctx) => {
const servers = Array.from(ctx.client.servers.values());
if (servers.length === 0) {
await ctx.reply('📭 Bot is not in any servers');
return;
}

const serverList = servers
.map((s, i) => `${i + 1}. **${s.name}** (\`${s.id}\`)`)
.join('\n');

await ctx.reply(`📋 **Servers (${servers.length})**\n\n${serverList}`);
},
});

Event Handling

client.on('ready', () => {
console.log('Bot is ready!');
});

client.on('message:new', (message, channelId) => {
console.log(`New message in ${channelId}: ${message.content}`);
});

client.on('user:status', (userId, status, user) => {
console.log(`${user.username} is now ${status}`);
});

client.on('disconnected', (reason) => {
console.log(`Bot disconnected: ${reason}`);
});

Command Context

Your command handler receives a CommandContext object with the following properties:

PropertyTypeDescription
commandstringThe command name that was invoked
argsstring[]Array of arguments (space-separated)
messageMessageThe message that triggered the command
channelChannelThe channel where the command was sent
clientChirpClientThe bot client instance
apiRESTClientThe API client for making requests

Context Methods

// Reply to the message
await ctx.reply('Hello!');

// Send a message without replying
await ctx.send('Hello!');

// Edit a message (only your own messages)
await ctx.edit(messageId, 'Updated content');

// Get the bot user info
const bot = ctx.client.user;

Available Intents

Configure which events your bot receives via the intents option:

IntentDescription
messagesReceive new message events
messageUpdatesReceive message edit/delete events
userStatusReceive user online/idle/dnd/offline status changes
typingReceive typing indicator events
serversReceive bot server join/leave events
pollsReceive poll vote events
threadsReceive thread events (reopen, user joined)
voiceReceive voice channel join/leave events

Running the Demo Bot

The repository includes a complete, working demo bot that showcases all SDK features.

Quick Start

# Navigate to the demo bot
cd examples/demo-bot-sdk

# Install dependencies
npm install

# Copy and edit environment file
cp .env.example .env
# Edit .env with your CHIRP_BOT_TOKEN and CHIRP_WS_URL

# Start the bot
npm start

Demo Commands

CommandDescription
/pingCheck bot latency
/helpList all available commands
/echo <text>Repeat your message back
/roll [max]Roll a random number (1-100 or custom max)
/serversList all servers the bot has access to
/choose <opt1> <opt2> ...Choose from multiple options
/say <text>Make the bot say something (without reply)
/flipFlip a coin
/8ball <question>Ask the magic 8-ball

Advanced Usage

Error Handling

client.commands.register(
new CommandBuilder()
.setName('risky')
.setHandler(async (ctx) => {
try {
await doSomethingRisky();
await ctx.reply('✅ Success!');
} catch (error) {
ctx.client.log.error(`Command failed:`, error);
await ctx.reply('❌ Something went wrong');
}
})
);

Middleware (Pre-checks)

client.commands.register(
new CommandBuilder()
.setName('admin')
.setDescription('Admin-only command')
.setHandler(async (ctx) => {
// Check if user has admin permissions
if (!ctx.message.member?.roles?.includes('admin')) {
await ctx.reply('❌ This command requires admin permissions');
return;
}
// ... rest of command
})
);

Sending Files

import { FormData } from 'formdata-node'; // or similar

client.commands.register(
new CommandBuilder()
.setName('upload')
.setHandler(async (ctx) => {
const form = new FormData();
form.append('file', fs.createReadStream('./image.png'));
form.append('content', 'Here is an image!');

await fetch(`${ctx.client.api.baseUrl}/channels/${ctx.channel.id}/messages`, {
method: 'POST',
headers: { 'Authorization': `Bot ${ctx.client.token}` },
body: form,
});
})
);

Direct API Reference

For advanced use cases where you need direct API access, the SDK provides a REST client:

// Get bot info
const botInfo = await client.api.getBotInfo();

// Get channels for a server
const channels = await client.api.getChannels(serverId);

// Get messages from a channel
const messages = await client.api.getMessages(channelId, { limit: 50 });

// Send a message
const message = await client.api.sendMessage(channelId, 'Hello!');

// Edit a message
await client.api.editMessage(messageId, 'Edited content');

// Delete a message
await client.api.deleteMessage(messageId);

WebSocket Events Reference

The SDK handles WebSocket connection and emits the following events:

EventParametersDescription
ready-Bot is connected and ready
message:new(message, channelId)New message created
message:updated(messageId, channelId, data)Message edited
message:deleted(messageId, channelId)Message deleted
user:status(userId, status, user)User status changed
typing:start(userId, channelId)User started typing
poll:voted(pollId, userId, optionId)Poll vote received
thread:reopened(threadId, channelId)Thread reopened
user_joined_thread(threadId, userId)User joined thread
voice:joined(userId, channelId, serverId)User joined voice
voice:left(userId, channelId, serverId)User left voice
bot:server_joined(serverId, serverName)Bot joined a server
bot:server_left(serverId, serverName)Bot left a server
disconnected(reason)WebSocket disconnected
reconnecting(attemptNumber)Reconnecting...
reconnected(attemptNumber)Reconnected successfully
error(error)An error occurred

Deployment

Running in Production

# Set production WebSocket URL
CHIRP_WS_URL=wss://your-domain.com

# Run with Node.js production flags
NODE_ENV=production npm start

Use PM2 or similar for production deployments:

npm install -g pm2
pm2 start npm --name "chirp-bot" -- start
pm2 startup
pm2 save

Limitations

Bots have some restrictions compared to regular users:

  • No admin actions — Bots cannot manage servers, channels, roles, or members