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:
| Property | Type | Description |
|---|---|---|
command | string | The command name that was invoked |
args | string[] | Array of arguments (space-separated) |
message | Message | The message that triggered the command |
channel | Channel | The channel where the command was sent |
client | ChirpClient | The bot client instance |
api | RESTClient | The 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:
| Intent | Description |
|---|---|
messages | Receive new message events |
messageUpdates | Receive message edit/delete events |
userStatus | Receive user online/idle/dnd/offline status changes |
typing | Receive typing indicator events |
servers | Receive bot server join/leave events |
polls | Receive poll vote events |
threads | Receive thread events (reopen, user joined) |
voice | Receive 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
| Command | Description |
|---|---|
/ping | Check bot latency |
/help | List all available commands |
/echo <text> | Repeat your message back |
/roll [max] | Roll a random number (1-100 or custom max) |
/servers | List all servers the bot has access to |
/choose <opt1> <opt2> ... | Choose from multiple options |
/say <text> | Make the bot say something (without reply) |
/flip | Flip 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:
| Event | Parameters | Description |
|---|---|---|
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
Process Manager (Recommended)
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