bot sdk - nself-org/nchat GitHub Wiki
Complete guide to building custom bots for nself-chat using the Bot SDK.
The nself-chat Bot SDK provides a powerful, type-safe way to create custom bots that can:
- Respond to messages and commands
- React to user events (join, leave, reactions)
- Store persistent state
- Schedule tasks
- Call external webhooks
- And much more!
✅ TypeScript-first - Full type safety and IntelliSense support ✅ Fluent API - Chain methods for readable code ✅ Event-driven - Subscribe to chat events easily ✅ Sandboxed - Safe execution environment ✅ Versioned - Track bot versions automatically ✅ Template system - Start from pre-built templates
The Bot SDK is built into nself-chat. No additional installation required!
Create a simple bot that responds to a command:
import { bot, text } from '@/lib/bots/bot-sdk'
export default bot('hello-bot')
.name('Hello Bot')
.description('A simple greeting bot')
.command('hello', 'Say hello', (ctx) => {
return text(`Hello, ${ctx.user.displayName}!`)
})
.build()Start from a pre-built template:
import { createWelcomeBot } from '@/lib/bots/templates'
// Create a welcome bot with default settings
const myWelcomeBot = createWelcomeBot()The bot() function returns a BotBuilder instance with a fluent API:
import { bot } from '@/lib/bots/bot-sdk'
const myBot = bot('my-bot-id')
.name('My Bot')
.description('What my bot does')
.version('1.0.0')
.icon('🤖')
.permissions('read_messages', 'send_messages')
.build()Bots respond to various events:
bot('echo-bot')
.name('Echo Bot')
.onMessage((ctx, api) => {
if (ctx.isMention) {
return text(`You said: "${ctx.message.content}"`)
}
})
.build()bot('poll-bot')
.name('Poll Bot')
.command('poll', 'Create a poll', async (ctx, api) => {
const question = ctx.args.question as string
const options = (ctx.args.options as string).split(',')
return embed()
.title(`📊 ${question}`)
.description(options.map((o, i) => `${i + 1}. ${o}`).join('\n'))
.build()
})
.build()bot('welcome-bot')
.name('Welcome Bot')
.onUserJoin((ctx, api) => {
return text(`Welcome ${api.mentionUser(ctx.user.id)} to #${ctx.channel.name}!`)
})
.onUserLeave((ctx, api) => {
return text(`Goodbye ${ctx.user.displayName}!`)
})
.build()bot('reaction-bot')
.name('Reaction Bot')
.onReaction((ctx, api) => {
if (ctx.reaction.emoji === '⭐' && ctx.reaction.action === 'add') {
return text('Someone starred a message!')
}
})
.build()Use keyword and pattern matching:
bot('faq-bot')
.name('FAQ Bot')
// Respond to keywords
.onKeyword(['help', 'support', 'faq'], (ctx, api) => {
return text('How can I help you?')
})
// Respond to patterns
.onPattern(['how do i.*', 'how to.*'], (ctx, api) => {
return text('Let me help you with that...')
})
.build()Set the bot's display name.
.name('My Awesome Bot')Set the bot's description.
.description('Helps users with common tasks')Set the bot's version (semantic versioning recommended).
.version('1.2.3')Set the bot's icon (emoji or URL).
.icon('🤖')Set required permissions.
.permissions('read_messages', 'send_messages', 'add_reactions')Set bot configuration.
.settings({
maxPollOptions: 10,
defaultDuration: 3600000, // 1 hour
})Register a command handler.
.command('ping', 'Check if bot is alive', (ctx) => {
return text('Pong!')
})Handle incoming messages.
.onMessage((ctx, api) => {
if (ctx.message.content.includes('bot')) {
return text('Did someone call me?')
}
})Handle user join events.
.onUserJoin((ctx, api) => {
return text(`Welcome ${ctx.user.displayName}!`)
})Handle user leave events.
.onUserLeave((ctx, api) => {
return text(`Goodbye ${ctx.user.displayName}!`)
})Handle reaction events.
.onReaction((ctx, api) => {
console.log(`${ctx.user.displayName} reacted with ${ctx.reaction.emoji}`)
})Run code when bot initializes.
.onInit(async (bot, api) => {
console.log('Bot started!', bot.manifest.name)
// Load data, set up scheduled tasks, etc.
})Send a simple text message.
return text('Hello, world!')Create a rich embed.
return embed()
.title('Hello')
.description('This is a rich embed')
.color('#6366f1')
.field('Field 1', 'Value 1', true)
.field('Field 2', 'Value 2', true)
.footer('Footer text')
.build()Send a success message.
return success('Operation completed successfully!')Send an error message.
return error('Something went wrong!')Send an info message.
return info('Here is some information...')Send a warning message.
return warning('Be careful!')The api parameter provides access to bot operations:
Send a message to a channel.
await api.sendMessage(ctx.channel.id, text('Hello!'))Reply to a specific message.
await api.replyToMessage(ctx.message.id, text('Replying!'))Add a reaction to a message.
await api.addReaction(ctx.message.id, '👍')Get channel information.
const channel = await api.getChannel(ctx.channel.id)
console.log(channel.name)Get user information.
const user = await api.getUser(ctx.user.id)
console.log(user.displayName)Create a user mention string.
const mention = api.mentionUser(ctx.user.id)
return text(`Hello ${mention}!`)Get value from persistent storage.
const data = await api.getStorage<MyData>('my-key')Save value to persistent storage.
await api.setStorage('my-key', { count: 42 })Delete value from storage.
await api.deleteStorage('my-key')Schedule a message for later delivery.
await api.scheduleMessage(
ctx.channel.id,
text('Reminder!'),
60000 // 1 minute
)import { bot, embed, parseDuration } from '@/lib/bots/bot-sdk'
export default bot('poll-bot')
.name('Poll Bot')
.description('Create polls and surveys')
.permissions('read_messages', 'send_messages', 'add_reactions')
.command('poll', 'Create a poll', async (ctx, api) => {
const question = ctx.args.question as string
const options = (ctx.args.options as string).split(',')
const pollId = Math.random().toString(36).substring(7)
await api.setStorage(`poll:${pollId}`, {
question,
options,
votes: {},
})
return embed()
.title(`📊 ${question}`)
.description(options.map((opt, i) => `${i + 1}. ${opt}`).join('\n'))
.footer(`Poll ID: ${pollId}`)
.build()
})
.build()import { bot, embed, list } from '@/lib/bots/bot-sdk'
interface FAQ {
question: string
answer: string
keywords: string[]
}
export default bot('faq-bot')
.name('FAQ Bot')
.description('Answer frequently asked questions')
.settings({
faqs: [
{
question: 'How do I reset my password?',
answer: 'Click "Forgot Password" on the login page.',
keywords: ['password', 'reset', 'forgot'],
},
] as FAQ[],
})
.onMessage(async (ctx, api) => {
const config = api.getBotConfig()
const faqs = (config.settings?.faqs as FAQ[]) || []
const message = ctx.message.content.toLowerCase()
// Find matching FAQ
const match = faqs.find((faq) => faq.keywords.some((keyword) => message.includes(keyword)))
if (match) {
return embed().title(`💡 ${match.question}`).description(match.answer).build()
}
})
.build()import { bot, text, parseDuration } from '@/lib/bots/bot-sdk'
export default bot('reminder-bot')
.name('Reminder Bot')
.description('Set reminders')
.command('remind', 'Set a reminder', async (ctx, api) => {
const message = ctx.args.message as string
const time = ctx.args.time as string
const delay = parseDuration(time)
await api.scheduleMessage(ctx.channel.id, text(`⏰ Reminder: ${message}`), delay)
return text(`Reminder set for ${time} from now!`)
})
.build()Always type your storage data:
interface PollData {
question: string
options: string[]
votes: Record<string, number>
}
const poll = await api.getStorage<PollData>('poll:123').onMessage(async (ctx, api) => {
try {
// Your code here
} catch (error) {
return error('Something went wrong. Please try again.')
}
}).command('poll', 'Create poll', (ctx) => {
if (!ctx.args.question || !ctx.args.options) {
return text('Usage: /poll question:<question> options:<opt1,opt2,opt3>')
}
// Create poll...
})Only request permissions you actually need:
.permissions('read_messages', 'send_messages')
// Don't request 'manage_messages' unless you need to delete messages.onInit((bot, api) => {
const interval = setInterval(() => {
// Do something periodically
}, 60000)
// Register cleanup
bot.registerCleanup(() => {
clearInterval(interval)
})
})// Good: Store only what you need
await api.setStorage('poll-count', 42)
// Bad: Storing redundant data
await api.setStorage('poll-count', {
count: 42,
timestamp: Date.now(),
user: ctx.user,
channel: ctx.channel,
// etc...
})let lastApiCall = (0).onMessage(async (ctx, api) => {
const now = Date.now()
if (now - lastApiCall < 1000) {
return // Rate limit: 1 call per second
}
lastApiCall = now
// Make external API call...
})Use decorators for class-based bot development:
import { BaseBot, Command } from '@/lib/bots/bot-sdk'
export class MyBot extends BaseBot {
constructor() {
super('my-bot', 'My Bot', 'A custom bot')
}
@Command('hello')
async handleHello(ctx: CommandContext) {
return text('Hello!')
}
@Command({ name: 'ping', description: 'Check bot status' })
async handlePing(ctx: CommandContext) {
return text('Pong!')
}
}Use bot state for complex data:
interface BotState {
activePolls: Map<string, Poll>
userPreferences: Map<string, UserPrefs>
}
.onInit(async (bot, api) => {
const state: BotState = {
activePolls: new Map(),
userPreferences: new Map(),
}
// Load from storage
const saved = await api.getStorage<any>('bot-state')
if (saved) {
state.activePolls = new Map(saved.activePolls)
}
})Call external webhooks:
.command('notify', 'Send notification', async (ctx) => {
const response = await fetch('https://api.example.com/webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: ctx.user.displayName,
message: ctx.args.message,
}),
})
if (response.ok) {
return success('Notification sent!')
} else {
return error('Failed to send notification')
}
})Run tasks on a schedule:
.onInit(async (bot, api) => {
// Daily reminder at 9 AM
const scheduleDaily = () => {
const now = new Date()
const target = new Date()
target.setHours(9, 0, 0, 0)
if (target < now) {
target.setDate(target.getDate() + 1)
}
const delay = target.getTime() - now.getTime()
setTimeout(async () => {
await api.sendMessage(
'general',
text('Good morning! Time for standup!')
)
scheduleDaily() // Reschedule for next day
}, delay)
}
scheduleDaily()
})- Check that the bot is enabled
- Verify permissions are granted
- Check bot logs for errors
- Test in sandbox mode
- Ensure unique keys
- Check storage quotas
- Clean up old data regularly
- Use expiration for temporary data
- Add rate limiting
- Cache frequently accessed data
- Optimize database queries
- Use pagination for large datasets
- GitHub: https://github.com/nself/nself-chat
- Discord: Join our community
- Docs: https://docs.nself.chat
-
Examples: Check
/lib/bots/templates/for more examples
Happy Bot Building! 🤖