Discord bots in TypeScript, from the gateway up.
A full client with caching, prefix and slash commands, components and voice, built from small packages you can also use on their own.
npm install detritus-clientyarn add detritus-clientpnpm add detritus-clientRequires Node 22.19 or newer.
import { CommandClient } from 'detritus-client';
const commandClient = new CommandClient(process.env.TOKEN!, {
prefix: '..',
});
commandClient.add({
name: 'ping',
run: (context) => context.reply('pong!'),
});
commandClient.add({
name: 'owner',
onBefore: (context) => context.client.isOwner(context.userId),
onCancel: (context) => context.reply('Owner only, sorry.'),
run: (context) => context.reply('Hello, owner.'),
});
(async () => {
const client = await commandClient.run();
console.log(`Running ${client.shardCount} shards`);
})();const { CommandClient } = require('detritus-client');
const commandClient = new CommandClient(process.env.TOKEN, {
prefix: '..',
});
commandClient.add({
name: 'ping',
run: (context) => context.reply('pong!'),
});
commandClient.add({
name: 'owner',
onBefore: (context) => context.client.isOwner(context.userId),
onCancel: (context) => context.reply('Owner only, sorry.'),
run: (context) => context.reply('Hello, owner.'),
});
(async () => {
const client = await commandClient.run();
console.log(`Running ${client.shardCount} shards`);
})();import { Constants, Interaction, InteractionCommandClient } from 'detritus-client';
const { ApplicationCommandOptionTypes } = Constants;
const interactionClient = new InteractionCommandClient(process.env.TOKEN!);
interactionClient.add({
name: 'avatar',
description: 'Show someone\'s avatar',
options: [
{
name: 'user',
description: 'Whose avatar to show',
type: ApplicationCommandOptionTypes.USER,
default: (context: Interaction.InteractionContext) => context.user,
},
],
run: (context, args) => context.editOrRespond(args.user.avatarUrl),
});
interactionClient.run();const { Constants, InteractionCommandClient } = require('detritus-client');
const { ApplicationCommandOptionTypes } = Constants;
const interactionClient = new InteractionCommandClient(process.env.TOKEN);
interactionClient.add({
name: 'avatar',
description: 'Show someone\'s avatar',
options: [
{
name: 'user',
description: 'Whose avatar to show',
type: ApplicationCommandOptionTypes.USER,
default: (context) => context.user,
},
],
run: (context, args) => context.editOrRespond(args.user.avatarUrl),
});
interactionClient.run();import { ShardClient } from 'detritus-client';
const client = new ShardClient(process.env.TOKEN!, {
gateway: {intents: ['GUILDS', 'GUILD_MESSAGES', 'MESSAGE_CONTENT']},
});
client.on('guildCreate', ({guild}) => {
console.log(`Joined ${guild.name} (${guild.memberCount} members)`);
});
client.on('messageCreate', async ({message}) => {
if (message.content === '!ping') {
await message.reply('pong!');
}
});
client.run();const { ShardClient } = require('detritus-client');
const client = new ShardClient(process.env.TOKEN, {
gateway: {intents: ['GUILDS', 'GUILD_MESSAGES', 'MESSAGE_CONTENT']},
});
client.on('guildCreate', ({guild}) => {
console.log(`Joined ${guild.name} (${guild.memberCount} members)`);
});
client.on('messageCreate', async ({message}) => {
if (message.content === '!ping') {
await message.reply('pong!');
}
});
client.run();import { Client } from 'detritus-client-rest';
const client = new Client(process.env.TOKEN!);
(async () => {
// ratelimits are queued and retried for you
const message = await client.createMessage(channelId, {
content: 'Sent over plain REST',
embed: {description: 'No websocket needed'},
});
await client.createReaction(channelId, message.id, '👍');
})();const { Client } = require('detritus-client-rest');
const client = new Client(process.env.TOKEN);
(async () => {
// ratelimits are queued and retried for you
const message = await client.createMessage(channelId, {
content: 'Sent over plain REST',
embed: {description: 'No websocket needed'},
});
await client.createReaction(channelId, message.id, '👍');
})();Five packages, one layer at a time
Each package is built on the ones below it and published on its own. Use the whole client, or drop down to raw REST and Gateway when you need the control.
- detritus-client0.17.0-beta.120 The full library: shards, clusters, caching, commands and voice. 232 classes431 interfaces1075 methods
The boring parts, handled
Buttons and menus that call you back
Give a component a run function and it's called when someone uses it. No matching custom ids by hand, and timeouts clean up after themselves.
const components = new Utils.Components({
timeout: 5 * 60 * 1000,
onTimeout: () => context.editOrRespond({content: 'Too slow!', components: []}),
});
components.createButton({
label: 'Click me',
run: (button) => button.editOrRespond({
content: `Clicked by ${button.user}`,
components: [],
}),
});
await context.editOrRespond({content: 'Go ahead', components});Scale without rewriting
Start with one shard and grow into processes. The event API stays the same.
Every endpoint is a method
Requests queue per ratelimit bucket and retry after a 429, global and shared limits included.
ClientCaches you control
Every collection can be capped, expired or switched off, from messages to presences.
Voice with DAVE
Join, send and receive audio with Discord's end-to-end encryption, and mix users together with AudioMixer.
Typed all the way down
Written in TypeScript, from raw gateway payloads in GatewayRawEvents up to every structure.