Nubis, an open-source framework by Eytak
Nubis is a TypeScript framework for Discord bots. Write a file and get a slash command, typed from what you wrote, with tests that run without Discord.
$ npx create-nubis my-botimport { command, hasPermission, option } from "nubis";
import { PermissionFlagsBits } from "discord-api-types/v10";
import { confirmBan } from "../../components/confirm-ban";
export default command({
description: "Ban a member",
ephemeral: true,
options: {
user: option.user({ required: true }),
days: option.integer({
choices: [
{ name: "One day", value: 1 },
{ name: "A week", value: 7 },
],
}),
},
guards: [hasPermission(PermissionFlagsBits.BanMembers)],
run: ({ options, reply }) =>
reply({
content: `Ban ${options.user.username}?`,
components: [confirmBan({ userId: options.user.id })],
}),
});
adaused /mod ban
my-botApp
Ban alice?
Only you can see this
commands/mod/ban.ts/mod ban
Pick a file. The path is the command.Types
Option values, choice unions, component state and modal fields are inferred from their definitions. There is no string to look anything up by.
days: option.integer({
choices: [
{ name: "One day", value: 1 },
{ name: "A week", value: 7 },
],
}),inferred
options.days: 1 | 7 | undefined
export const confirmBan = button({
id: "confirm-ban",
label: "Confirm",
state: z.object({ userId: z.string() }),
onClick: ({ state }) => …inferred
state.userId: string
fields: {
reason: textInput({
label: "Reason",
required: true,
}),
contact: textInput({ label: "Contact" }),
},inferred
values.contact: string | undefined
Discord's rules
Command names of up to 32 characters, at most 25 options and 25 choices, 100-character custom_ids, 5 rows of components. A definition Discord would reject fails the moment it loads, and the error says where.
days: option.integer({
choices: [
{ name: "One day", value: 1 },
{ name: "A week and a half", value: 7.5 },
],
}),$ nubis dev
[nubis] Synced commands:
+ ping
+ mod
[nubis] commands/mod/ban.ts changed, restarting.
[nubis] /mod ban option "days": integer choices must be whole numbers.
[nubis] Fix the error and save; I'll try again.
Testing
nubis/testing runs commands, buttons, selects, modals and menus the way Discord would. No token, no network, no test server.
import { PermissionFlagsBits } from "discord-api-types/v10";
import { fake, testCommand } from "nubis/testing";
import ban from "../commands/mod/ban";
test("asks for confirmation", async () => {
const result = await testCommand(ban)
.as({ permissions: PermissionFlagsBits.BanMembers })
.run({ user: fake.user("alice") });
expect(result.replies).toEqual(["Ban alice?"]);
expect(result.ephemeral).toBe(true);
});
test("members without the permission are turned away", async () => {
const result = await testCommand(ban).run({ user: fake.user("alice") });
expect(result.status).toBe("denied");
});$ bun test
tests/ban.test.ts:
✓ asks for confirmation
✓ members without the permission are turned away
2 pass
0 fail
Replies
Discord gives a bot 3 seconds to answer. Call reply() and Nubis handles the rest: the waiting message, the edit and the follow-ups.
0 s
Your handler starts building the report.
2 s
After 2 seconds without an answer, before Discord's 3-second limit runs out.
When it's ready
The first reply replaces “thinking…”.
Within 15 min
Later replies are new messages, in the order you sent them.
Discord's limit · 3 s