@mundobobba/mbjs
v1.0.1
Published
Official SDK for building bots on the Mundo Bobba platform
Maintainers
Readme
@mundobobba/bot-sdk
Official SDK for building bots on the Mundo Bobba platform.
Installation
npm install @mundobobba/bot-sdkSystem requirements (for music bots)
- yt-dlp installed and available in PATH
- FFmpeg installed and available in PATH
- A
cookies.txtfile from YouTube in your bot's working directory
Quick start
import { MundobobbaBot } from '@mundobobba/bot-sdk';
const bot = new MundobobbaBot({
token: process.env.BOT_TOKEN!,
prefix: '!',
});
bot.on('ready', () => {
console.log(`Bot online as ${bot.botName}`);
});
bot.textCommand('ping', (ctx) => {
ctx.reply('Pong!');
});Getting your token
- Go to mundobobba.com and sign in
- Navigate to Messages > Bots
- Create a new bot and copy the generated token
- Invite the bot to a conversation using the invite link
Text commands
Register commands that respond to messages starting with your prefix.
const bot = new MundobobbaBot({ token: '...', prefix: '!' });
// Simple reply
bot.textCommand('hello', (ctx) => {
ctx.reply(`Hello, ${ctx.invokedBy.name}!`);
});
// Access command arguments
bot.textCommand('echo', (ctx) => {
ctx.reply(ctx.args || 'Nothing to echo.');
});
// Built-in commands: !ping, !uptime, !help
bot.useBuiltins();Command context
Every command handler receives a CommandContext with:
| Property | Type | Description |
|---|---|---|
| command | string | Command name |
| args | string | Arguments after the command |
| convId | number | Conversation ID |
| invokedBy | { id, name } | User who invoked |
| reply(message) | function | Public reply (everyone sees) |
| replyEphemeral(message) | function | Reply only the invoker sees |
| deferReply() | function | Show "thinking..." indicator |
| followUp(message) | function | Additional message in same block |
Embeds
Send rich cards with title, description, color, fields, images, and more.
bot.textCommand('stats', async (ctx) => {
const user = await bot.getUser(ctx.invokedBy.id);
ctx.reply({
embed: {
title: user.name,
description: `Level ${user.level}`,
color: '#5865F2',
thumbnail: `https://habbo.com/avatar/${user.name}`,
fields: [
{ name: 'Coins', value: user.coin.toLocaleString(), inline: true },
{ name: 'XP', value: user.xp.toLocaleString(), inline: true },
],
footer: { text: 'Mundo Bobba' },
timestamp: new Date().toISOString(),
},
});
});Buttons
Add interactive buttons to your messages.
bot.textCommand('confirm', (ctx) => {
ctx.reply({
content: 'Are you sure?',
components: [{
type: 'action_row',
components: [
{ type: 'button', style: 'success', label: 'Yes', customId: 'yes' },
{ type: 'button', style: 'danger', label: 'No', customId: 'no' },
{ type: 'button', style: 'link', label: 'Docs', url: 'https://mundobobba.com' },
],
}],
});
});
bot.on('interaction', (ctx) => {
if (ctx.customId === 'yes') ctx.reply('Confirmed!');
if (ctx.customId === 'no') ctx.replyEphemeral('Cancelled.');
});Button styles
| Style | Appearance |
|---|---|
| 'primary' | Blue (default action) |
| 'secondary' | Gray (secondary action) |
| 'success' | Green (confirm) |
| 'danger' | Red (destructive) |
| 'link' | Opens a URL (no interaction event) |
Select menus
Add dropdown menus to your messages.
bot.textCommand('class', (ctx) => {
ctx.reply({
embed: { title: 'Choose your class', color: '#FF9900' },
components: [{
type: 'action_row',
components: [{
type: 'select',
customId: 'class_select',
placeholder: 'Select a class...',
options: [
{ label: 'Warrior', value: 'warrior', emoji: '⚔️' },
{ label: 'Mage', value: 'mage', emoji: '🧙' },
{ label: 'Archer', value: 'archer', emoji: '🏹' },
],
}],
}],
});
});
bot.on('interaction', (ctx) => {
if (ctx.customId === 'class_select') {
ctx.reply(`You chose: ${ctx.values[0]}`);
}
});Deferred replies
Show a "thinking..." indicator while processing.
bot.textCommand('weather', async (ctx) => {
const deferred = ctx.deferReply();
const data = await fetchWeatherAPI(ctx.args);
deferred.editReply({
embed: {
title: `Weather in ${data.city}`,
description: `${data.temp} - ${data.condition}`,
color: '#87CEEB',
},
});
});Ephemeral messages
Send messages only the invoker can see.
bot.textCommand('secret', (ctx) => {
ctx.replyEphemeral(`Only you can see this, ${ctx.invokedBy.name}!`);
});Modals / Forms
Open a form dialog and handle submissions.
bot.textCommand('feedback', (ctx) => {
bot.showModal(ctx.convId, ctx.invokedBy.id, {
customId: 'feedback_form',
title: 'Send Feedback',
fields: [
{ type: 'text', customId: 'subject', label: 'Subject', required: true },
{ type: 'textarea', customId: 'body', label: 'Message', maxLength: 500 },
],
});
});
bot.on('modalSubmit', (ctx) => {
if (ctx.customId === 'feedback_form') {
ctx.reply(`Thanks for your feedback, ${ctx.invokedBy.name}!`);
console.log(ctx.values); // { subject: '...', body: '...' }
}
});Collectors
Wait for messages or interactions with a timeout.
// Wait for a message
bot.textCommand('ask', async (ctx) => {
ctx.reply('What is your favorite color? (15s)');
const messages = await bot.awaitMessages(ctx.convId, {
filter: (m) => m.senderId === ctx.invokedBy.id,
timeout: 15_000,
max: 1,
});
if (messages.length > 0) {
ctx.followUp(`You said: ${messages[0].content}`);
} else {
ctx.followUp('Time is up!');
}
});
// Wait for a button click
const clicks = await bot.awaitInteraction(ctx.convId, {
filter: (i) => i.customId === 'confirm',
timeout: 30_000,
max: 1,
});Paginated embeds
Navigate between pages using buttons.
import { PaginatedEmbed } from '@mundobobba/bot-sdk';
bot.textCommand('rules', (ctx) => {
const pages = [
{ title: 'Rules — General', description: '1. Be respectful\n2. No spam', color: '#5865F2' },
{ title: 'Rules — Chat', description: '3. Use correct channels\n4. No flood', color: '#5865F2' },
{ title: 'Rules — Trade', description: '5. No scam\n6. Fair prices', color: '#5865F2' },
];
const paginator = new PaginatedEmbed(pages);
paginator.send(ctx);
// Handle page navigation
bot.on('interaction', (i) => paginator.handleInteraction(i));
});Confirm dialogs
Ask yes/no questions with a Promise.
import { ConfirmDialog } from '@mundobobba/bot-sdk';
bot.textCommand('reset', async (ctx) => {
const confirmed = await ConfirmDialog.ask(ctx, {
title: 'Confirm Reset',
description: 'Are you sure you want to reset your data?',
color: '#FF0000',
});
if (confirmed) ctx.followUp('Data has been reset.');
else ctx.followUp('Cancelled.');
});
// Required: handle confirm dialog interactions
bot.on('interaction', (ctx) => ConfirmDialog.handleInteraction(ctx.customId));Presence
Set the bot's status and activity.
bot.setPresence({
status: 'online',
activity: { type: 'listening', name: 'Lo-fi Hip Hop' },
});| Status | Description |
|---|---|
| 'online' | Green indicator |
| 'idle' | Yellow indicator |
| 'dnd' | Red indicator |
| Activity type | Display |
|---|---|
| 'playing' | Playing [name] |
| 'listening' | Listening to [name] |
| 'watching' | Watching [name] |
| 'streaming' | Streaming [name] |
Event listeners
// Any chat message in conversations with the bot
bot.on('message', (msg) => {
console.log(`${msg.senderName}: ${msg.content}`);
});
// User reacted to a message
bot.on('reaction', (data) => {
console.log(`${data.userName} reacted with ${data.emoji}`);
});
// User joined a conversation
bot.on('memberJoin', (data) => {
bot.sendMessage(data.convId, `Welcome, ${data.userName}!`);
});
// User left a conversation
bot.on('memberLeave', (data) => {
bot.sendMessage(data.convId, `${data.userName} left.`);
});
// Someone mentioned @BotName
bot.on('mention', (data) => {
bot.sendMessage(data.convId, `You called, ${data.userName}?`);
});
// A message was edited
bot.on('messageEdit', (data) => {
console.log(`Message ${data.messageId} was edited.`);
});
// A message was deleted
bot.on('messageDelete', (data) => {
console.log(`Message ${data.messageId} was deleted.`);
});Platform API
Query platform data directly from your bot.
// Get user info
const user = await bot.getUser(userId);
console.log(user.name, user.level, user.coin);
// Get user permissions
const perms = await bot.getPermissions(userId);
if (!perms.isAdmin) ctx.replyEphemeral('No permission.');
// Get conversation info
const conv = await bot.getConversation(convId);
console.log(conv.name, conv.memberCount, conv.members);
// Get user profile (full)
const profile = await bot.api('getUserProfile', { name: 'PlayerOne' });Scheduled messages
Send messages at a specific time.
await bot.schedule(convId, 'Good morning!', new Date('2026-04-06T08:00:00Z'));
await bot.schedule(convId, {
embed: { title: 'Reminder', description: 'Meeting starts now!', color: '#FF0000' },
}, new Date(Date.now() + 60_000)); // 1 minute from nowMusic bot
Build a music bot with audio streaming.
import { MundobobbaBot, AudioStreamer, YouTubeResolver, QueueService } from '@mundobobba/bot-sdk';
const TOKEN = process.env.BOT_TOKEN!;
const bot = new MundobobbaBot({ token: TOKEN, prefix: '!' });
const streamer = new AudioStreamer({ token: TOKEN });
const youtube = new YouTubeResolver();
const queue = new QueueService();
bot.textCommand('play', async (ctx) => {
const result = await youtube.search(ctx.args);
if (!result) { ctx.reply('Not found.'); return; }
if (streamer.isStreaming(ctx.convId)) {
queue.add(ctx.convId, { ...result, addedBy: ctx.invokedBy.name });
ctx.reply(`Added to queue: ${result.title}`);
return;
}
ctx.reply(`Now playing: ${result.title}`);
bot.broadcastMusicPlaying(ctx.convId, result);
await streamer.start(ctx.convId, result.url, (convId) => {
const next = queue.shift(convId);
if (!next) { bot.broadcastMusicStopped(convId); return; }
bot.broadcastMusicPlaying(convId, next);
streamer.start(convId, next.url, () => {});
});
});
bot.textCommand('stop', (ctx) => {
streamer.stop(ctx.convId);
queue.clear(ctx.convId);
bot.broadcastMusicStopped(ctx.convId);
ctx.reply('Stopped.');
});Rate limiter
Prevent command spam.
import { RateLimiter } from '@mundobobba/bot-sdk';
// Per user (default)
const limiter = new RateLimiter();
// Per conversation
const limiter = new RateLimiter({ mode: 'conversation' });
// Custom cooldowns
const limiter = new RateLimiter({
cooldowns: { play: 5000, skip: 2000 },
});
bot.textCommand('play', (ctx) => {
const result = limiter.check(ctx.invokedBy.id, 'play');
if (!result.allowed) {
ctx.replyEphemeral(`Wait ${result.remainingSec}s.`);
return;
}
// ... handle command
});Edit and delete messages
// Edit a bot message
bot.editMessage('bot_123', convId, 'Updated content');
// Delete a bot message
bot.deleteMessage('bot_123', convId);Slash commands
Register commands that appear in the platform's command picker.
bot.on('ready', () => {
bot.registerCommands([
{
name: 'play',
description: 'Play a song',
options: [{ name: 'query', description: 'Song name or URL', required: true }],
},
{ name: 'stop', description: 'Stop playback' },
]);
});
bot.on('command', (ctx) => {
if (ctx.command === 'play') {
// Handle slash command
}
});API Reference
MundobobbaBot
| Method | Description |
|---|---|
| textCommand(name, handler) | Register a text command |
| useBuiltins() | Register ping, uptime, help |
| registerCommands(commands) | Register slash commands |
| sendMessage(convId, message) | Send public message |
| sendEphemeral(convId, message, userId) | Send ephemeral message |
| editMessage(msgId, convId, message) | Edit bot message |
| deleteMessage(msgId, convId) | Delete bot message |
| showModal(convId, userId, modal) | Open modal dialog |
| setPresence(presence) | Set bot status |
| broadcastMusicPlaying(convId, info) | Broadcast now playing |
| broadcastMusicStopped(convId) | Broadcast music stopped |
| broadcastQueueUpdated(convId, queue) | Update queue UI |
| api(method, params) | Call platform API |
| getUser(userId) | Get user info |
| getConversation(convId) | Get conversation info |
| getPermissions(userId) | Get user permissions |
| schedule(convId, message, date) | Schedule a message |
| awaitMessages(convId, options) | Wait for messages |
| awaitInteraction(convId, options) | Wait for interactions |
Events
| Event | Payload | Description |
|---|---|---|
| ready | — | Bot connected |
| command | CommandContext | Slash command invoked |
| message | ChatMessage | Any chat message |
| interaction | ComponentInteraction | Button/select interaction |
| reaction | ReactionEvent | Message reaction |
| memberJoin | MemberEvent | User joined |
| memberLeave | MemberEvent | User left |
| mention | { userId, userName, convId, content } | Bot was mentioned |
| modalSubmit | ModalSubmitEvent | Modal form submitted |
| messageEdit | { messageId, content, convId } | Message edited |
| messageDelete | { messageId, convId } | Message deleted |
| disconnect | — | WebSocket disconnected |
| error | Error | WebSocket error |
License
MIT
