Bots Open Roar

Make a bot

Roar.js reference

Roar.js is Roar's bot framework: one file with no dependencies that handles connecting, reconnecting, heartbeats, rate limits and slash commands, so your code only says what the bot does. Version 1.0.0.

Install

Terminal
npm install https://bots.roarnode.com/roar.js.tgz

It needs Node.js 22 or newer (for its built-in WebSocket), with "type": "module" in your package.json. It also works in Deno straight from the web, and in Bun:

Deno
import { Bot } from "https://bots.roarnode.com/roar.js";

Prefer a single file? Download roar.js (and roar.d.ts for autocomplete), put it next to your bot and import "./roar.js".

The Bot

JavaScript
import { Bot } from "roar.js";

const bot = new Bot({
  token: process.env.ROAR_TOKEN,       // the bot's token (this is the default)
  presence: { activity: "for /help", activityType: "watching" },
});

bot.on("ready", () => console.log(`Online as ${bot.user.username}`));
await bot.start();                      // resolves once the bot is ready
OptionWhat it does
tokenThe bot's token. Left out, Roar.js reads the ROAR_TOKEN environment variable.
nodeWhich node to join. Left out, it's the official node. See Which node. Defaults to ROAR_NODE.
relayThe relay that finds nodes by id: https://roarnode.com.
presence{ status, activity, activityType, customStatus }, shown from the moment it connects. See Presence.
ownMessagestrue also passes the bot's own messages to "message". Off by default, so a bot can't answer itself in a loop.

What the bot has

Property or method
bot.userThe bot's own User (once ready).
bot.applicationIdThe Application ID.
bot.guildsA Map of the servers it's in, by id. bot.guild("Bot Lab") finds one by id or name.
bot.channels, bot.usersMaps of every channel and person the bot knows about. bot.channel(id) looks one up.
bot.start() / bot.stop()Connect (and stay connected, reconnecting by itself) / disconnect for good. start() rejects if the token is refused.
bot.send(channel, content)Send a message to a channel (a Channel or an id). See Sending messages.
bot.dm(user, content)Send a direct message. The bot and the person need to share a server.
bot.fetchUser(id)Look up anyone by id.
bot.restAny API route not wrapped here: bot.rest.get("/users/@me"), .post, .put, .patch, .delete.

Which node

A bot belongs to the node where it was made. Most bots live on the official node, which is the default. For any other node, pass node:

JavaScript
new Bot({ token });                                   // the official node, through roarnode.com
new Bot({ token, node: "mk3vancmfm" });               // a node by its id (through the relay)
new Bot({ token, node: "https://chat.example.com" }); // a node with its own address
new Bot({ token, node: "http://127.0.0.1:8080" });    // a node on this PC, for testing

A node's id is on the Developer page of the Roar app, in Getting started.

Events

Listen with bot.on(event, handler), bot.once for just the next one, and bot.off to stop. Handlers can be async; if one throws, the error goes to "error" (or the console) and the bot keeps running.

EventHandler getsWhen
readythe botConnected, with every server loaded. After a reconnect it fires again.
messageMessageSomeone sends a message the bot can see (not the bot's own, unless ownMessages).
messageUpdateMessageA message is edited.
messageDelete{ id, channelId, guildId }A message is deleted.
reactionAdd, reactionRemoveReactionSomeone else adds or removes a reaction.
memberJoinMemberSomeone joins a server the bot is in.
memberLeaveMember (or { id, user, guild })Someone leaves or is removed.
memberUpdateMemberTheir roles, nickname or timeout change.
guildJoin, guildLeaveGuildThe bot is added to, or removed from, a server.
guildUpdateGuildA server's settings change.
commandInteractionAny slash command (as well as its own handler).
typing{ userId, channelId, guildId }Someone starts typing.
presencethe raw presenceSomeone's status or activity changes.
raw(type, data)Every gateway event, unchanged: see the event list.
disconnect, reconnecting{ code, reason }, { delay }The connection dropped; it's trying again in delay ms.
error, debugError, textSomething went wrong / what it's doing, for logs.

Messages

JavaScript
bot.on("message", async (msg) => {
  if (msg.author.isBot) return;                  // ignore other bots
  if (msg.content.startsWith("!say ")) {
    await msg.channel.send(msg.content.slice(5));
  }
  if (msg.mentionsMe) await msg.react("๐Ÿ‘€");     // someone pinged the bot
});
Message
id, content, createdAt, editedAtThe basics. content holds up to 4000 characters.
authorWho sent it (a User). author.isBot, author.isWebhook.
member, guild, channelTheir membership, the server and the channel. guild and member are null in DMs (msg.isDM).
mentions, mentionsMe, mentionsEveryoneIds of the people it pings, and whether it pings the bot or everyone.
attachments[{ id, name, size, contentType, width, height, url }], with full download addresses.
embeds, reactions, replyTo, pinnedIts embeds, its reactions ({ emoji, count, me }), the message it replies to, and whether it's pinned.
reply(content)Reply to it (pings the author; pass { content, ping: false } not to).
react(emoji), unreact(emoji)"๐Ÿ‘‹", or a server emoji as "name:id".
edit(content), delete(), pin(), unpin()Edit the bot's own messages; delete, pin and unpin need permission for other people's.

Sending messages

Anything that sends (channel.send, msg.reply, bot.send, user.send) takes text, or an object:

JavaScript
await channel.send("Hello!");
await channel.send({
  content: "Today's weather",
  embed: { title: "Sunny", description: "22ยฐC, light wind", color: "#ffb86b" },
  files: ["./forecast.png"],             // paths, or { name, data }
  silent: true,                          // no notification, like @silent
});
Field
contentText, up to 4000 characters. Markdown works: **bold**, *italic*, `code`, ||spoiler||.
embed / embedsOne card, or up to 10. See Embeds.
filesUp to 10 files: paths on disk, or { name, data } with a Uint8Array, Buffer or Blob. The node's upload limit applies (25 MB by default; 8 MB through the relay).
silentSend without notifying anyone.
replyTo, pingMake it a reply to a message, and choose whether that pings its author.

Mentions: put a user, member, channel or role in a template string and it turns into a ping, or use the helpers:

JavaScript
import { mention, channelMention, roleMention } from "roar.js";

await channel.send(`Hi ${msg.author}!`);                       // pings them
await channel.send(`Read ${channelMention(rulesId)} first, ${mention(userId)}`);

Embeds

Embeds are cards with a coloured edge, a title, fields and pictures. Only bots and webhooks can send them.

JavaScript
await i.reply({
  embed: {
    title: "Server stats",
    url: "https://roarnode.com",
    description: "How we're doing this week",
    color: "#5fd96a",                     // or a number like 0x5fd96a
    author: "Stats Bot",
    thumbnail: "https://example.com/logo.png",
    fields: [
      { name: "Members", value: "128", inline: true },
      { name: "Messages", value: "4,210", inline: true },
    ],
    image: "https://example.com/chart.png",
    footer: "Updated every hour",
    timestamp: new Date(),
  },
});

Up to 10 embeds per message, about 12 KB of text in total.

Slash commands

Add commands with bot.command. Roar.js sends them all to the node each time the bot connects, and runs the handler when someone uses one.

JavaScript
// The short way: a name, a description, a handler.
bot.command("ping", "Check the bot is awake", (i) => i.reply("Pong!"));

// With options.
bot.command({
  name: "remind",
  description: "Remind me later",
  options: [
    { name: "minutes", description: "In how many minutes", type: "integer", required: true },
    { name: "what", description: "What to remind you about", type: "string", required: true },
    { name: "where", description: "Which channel", type: "channel" },
  ],
}, async (i) => {
  await i.reply({ content: `OK, in ${i.options.minutes} min`, ephemeral: true });
});

Options

typei.options.name is
"string" (the default)text
"integer", "number"a whole number, any number
"boolean"true or false
"user"a user id; i.optionUser("name") gives the User
"channel"a channel id; i.optionChannel("name") gives the Channel
"role"a role id; i.optionRole("name") gives the Role

Each option can have required: true and up to 25 choices, like choices: [4, 6, 20] or [{ name: "Heads", value: "h" }]. Rules the node checks, and Roar.js checks first: names are 1โ€“32 lowercase letters, numbers, - or _; descriptions are 1โ€“100 characters; at most 25 options; required options come first; at most 100 commands. An option left empty is missing from i.options.

Answering

Interaction
reply(content)Answer it: text, or { content, embed, embeds, ephemeral, silent }. ephemeral: true shows the answer only to the person who used the command.
defer({ ephemeral })Show "Bot is thinking..." while you work. Then answer with reply or editReply, within 15 minutes.
editReply(content)Change the answer.
user, member, guild, channelWho used it, and where.
commandName, optionsThe command, and the options by name.

If your handler throws before answering, Roar.js answers "Something went wrong running that command." to that person only, and reports the error.

What isn't there yet: buttons, menus, pop-up forms, autocomplete, follow-up messages and commands for a single server. Commands are used in server channels from the Roar desktop app.

Servers, channels and roles

JavaScript
const guild = bot.guild("Bot Lab");          // or msg.guild, i.guild, member.guild
const welcome = guild.channel("welcome");    // by name or id
const mods = guild.role("Moderator");

await welcome.send(`We're ${guild.memberCount} now!`);
await guild.addRole(userId, "Member");       // a role by name, id or Role
Guild (a server)
id, name, ownerId, memberCountThe basics.
channels, roles, membersMaps by id. channel(nameOrId), role(nameOrId) and member(userOrId) find one; systemChannel is where join messages go.
addRole(user, role), removeRole(user, role)Needs Manage Roles, and the bot's role above that role.
kick(user), ban(user), unban(user)Needs Kick Members / Ban Members, and to outrank them.
timeout(user, minutes)Stops them talking for a while (Timeout Members); 0 lifts it.
fetchMember(user), leave()Get a member fresh from the node / take the bot out of the server.

Every moderation method takes an optional last argument, a reason, which shows in the server's audit log: guild.kick(id, "Spamming").

Channel
id, name, type, topic, guildtype is "text", "voice", "announcement", "forum", "thread", "dm"...
send(content), typing()Send a message / show "Bot is typing..." for a few seconds.
messages({ limit, before, after }), fetchMessage(id)Read the channel's history (up to 100 at a time, newest first).
createWebhook(name)Make a webhook for this channel (Manage Webhooks).

Users and members

A User is a person (or bot) anywhere on the node. A Member is that person in one server, with their nickname and roles.

User
id, username, displayName, isBot, badgesWho they are.
avatarUrl, createdAt, mentionTheir picture's address (or null), when the account was made, and <@id>.
send(content)DM them.
Member
user, guild, displayName, nick, roles, joinedAtTheir nickname wins over the user's display name. roles holds role ids.
hasRole(role), addRole(role), removeRole(role)By name, id or Role.
kick(), ban(), timeout(minutes), send(content)The same as on the Guild.

Reactions

JavaScript
bot.on("reactionAdd", async (r) => {
  if (r.emoji === "โญ") {
    const msg = await r.fetchMessage();
    await bot.send(starboardId, `โญ ${msg.author.displayName}: ${msg.content}`);
  }
});

A reaction has emoji ("โญ", or "name:id" for a server emoji), userId, user, member, messageId, channelId, guild, roar (a Roar super reaction) and fetchMessage(). The bot's own reactions don't fire these events.

Presence

JavaScript
bot.setActivity("for /help", "watching");     // "Watching for /help"
bot.setPresence({ status: "idle", customStatus: "Taking a nap" });

status is "online", "idle", "dnd" or "invisible". Activity types are playing, listening, watching, streaming and competing.

Webhooks

Webhook posts to a channel through a webhook link. It doesn't need a bot or a token. More about webhooks โ†’

JavaScript
import { Webhook } from "roar.js";

const hook = new Webhook(process.env.ROAR_WEBHOOK_URL);
await hook.send({ content: "Deploy finished โœ…", username: "CI", embed: { title: "v1.4.2", color: "#5fd96a" } });

Errors

When the node turns a request down, the promise rejects with a RoarError that has status (the HTTP status), code (Roar's error code) and a readable message:

JavaScript
import { RoarError } from "roar.js";

try {
  await member.kick();
} catch (e) {
  if (e instanceof RoarError && e.code === 50013) await msg.reply("I'm not allowed to kick them.");
  else throw e;
}

Rate limits (10 messages every 5 seconds per account, plus a channel's slowmode) are handled for you: Roar.js waits and tries again. Common error codes โ†’

Helpers

Export
mention(user), channelMention(channel), roleMention(role)<@id>, <#id>, <@&id> from an object or an id.
timestampOf(id)When anything with an id was made, as a Date.
VERSIONThe Roar.js version: "1.0.0".

Everything in the cache also keeps the node's raw object as .raw, for fields Roar.js doesn't name yet.