@dsqr/discord
v0.0.1-beta.2
Published
<div align="center">
Readme
DSQR Discord
The DSQR discord package provides a simple and efficient way to get started with a Discord bot. It handles the initial plumbing, allowing you to build from the ground up to support all your needs.
- Quick Start: Get a bot running with minimal code, leveraging Discord.js and Bun's SQLite (or other databases upon request).
- Customizable: Add your own intents, commands, event handlers, and lifecycle callbacks.
- Database Included: Built-in SQLite support with guild tracking, fully extensible for custom tables and queries.
- Type Safety: Written in TypeScript with Zod validation for configuration.
- Lifecycle Hooks: Comprehensive callbacks for startup, readiness, shutdown, commands, errors, and more.
⇁ TOC
- The Problems
- The Solutions
- Installation
- Getting Started
- Custom Event Handlers
- Extending the Database
- Lifecycle Callbacks
- Handling Errors and Shutdowns
- Nix Development Setup
- Tips for Success
⇁ The Problems
- Setting up a Discord bot from scratch involves tedious boilerplate: initializing the client, managing events, registering commands, and handling persistence.
- You need a starting point that's simple yet flexible enough to scale—whether it's custom commands, event handling, or database storage.
- Managing environment variables and ensuring they're valid can be a hassle without a clear, type-safe approach.
⇁ The Solutions
- DSQR Discord provides a pre-configured Discord.js client with built-in event handlers and SQLite integration, reducing setup time.
- A modular design lets you customize intents, commands, event handlers, and lifecycle callbacks while exposing the full database for expansion.
- A Zod-based configuration system validates environment variables, making setup reliable and straightforward.
⇁ Installation
Install using npm (or your preferred package manager):
bun add @dsqr/discord @ai-sdk/perplexityIf you're using Nix for development, see the Nix Development Setup section below.
⇁ Getting Started
DSQR Discord makes it incredibly easy to set up a Discord bot. The package handles all the complex boilerplate like client initialization, command registration, event handling, and database setup.
How It Works
When you start your bot with bot.start(), DSQR Discord will:
- Set up default event handlers (which can be overridden via
eventHandlers) - Register your slash commands with Discord
- Log in to Discord using your bot token
- Call your
onStartcallback if provided - Automatically track guild membership in the database
The bot comes with sensible defaults but is fully customizable:
Prerequisites
- A Discord bot token and client ID from the Discord Developer Portal.
- An
.envfile with required variables (see below). - For the
ChatbotCommand, a Perplexity API key (optional, addPERPLEXITY_API_KEY=your-keyto.env).
Basic Setup
Set Up Environment Variables: Create an
.envfile in your project root:DISCORD_BOT_TOKEN=your-bot-token DISCORD_CLIENT_ID=your-client-id DISCORD_DB_PATH=dsqr.local.db # Optional, defaults to this PERPLEXITY_API_KEY=your-perplexity-key # Optional, for ChatbotCommandCreate Your Bot: Here's a simple example with commands using
satisfies:// bot.ts import { local, dsqrDiscord, DsqrDiscordConfig } from "@dsqr/discord" import { GatewayIntentBits } from "discord.js" import { PingPongCommand } from "./commands/ping.ts" import { ChatbotCommand } from "./commands/chat.ts" // Load config from .env const config = local.getConfig() const botConfig = { botToken: config.discord.botToken, clientId: config.discord.clientId, database: { type: "sqlite", filename: config.discord.dbPath }, intents: [GatewayIntentBits.GuildMessages], commands: [new PingPongCommand(), new ChatbotCommand()], callbacks: { onStart: () => console.log("Bot started!"), onReady: (client) => console.log(`Ready as ${client.user.tag}`), onError: (error) => console.error("Error:", error.message), }, } satisfies DsqrDiscordConfig const bot = dsqrDiscord(botConfig) bot.start()Run Your Bot: With Bun:
bun run bot.ts
Adding Commands
Here are two example commands:
PingPong Command:
// commands/ping.ts import { Command } from "@dsqr/discord" import { SlashCommandBuilder } from "discord.js" export class PingPongCommand implements Command { name = "ping" slashCommandConfig = new SlashCommandBuilder() .setName(this.name) .setDescription("Replies with Pong!") async execute(interaction) { await interaction.reply("Pong!"); } }Simple Chatbot Command with Perplexity:
// commands/chat.ts import { Command } from "@dsqr/discord" import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js" import { createPerplexity } from "@ai-sdk/perplexity" const perplexity = createPerplexity({ apiKey: process.env.PERPLEXITY_API_KEY ?? "", }) export class ChatbotCommand implements Command { name = "chat" slashCommandConfig = new SlashCommandBuilder() .setName(this.name) .setDescription("Chat with an AI assistant") .addStringOption((option) => option .setName("message") .setDescription("Your message") .setRequired(true) ) async execute(interaction: ChatInputCommandInteraction) { await interaction.deferReply(); const message = interaction.options.getString("message"); if (!message) return interaction.editReply("Please provide a message."); try { const response = await perplexity("sonar-pro").chat({ messages: [{ role: "user", content: message }], }); await interaction.editReply(response.choices[0].message.content); } catch (error) { console.error("Chat error:", error); await interaction.editReply("Sorry, I couldn't respond. Try again later."); } } }
⇁ Custom Event Handlers
You can override default event handlers or add new ones, and use the exposed db.database for custom queries. DSQR Discord automatically handles the following events: ClientReady, Error, GuildCreate, GuildDelete, and InteractionCreate, but you can customize any of these or add additional event handlers.
Here's an example logging messages to a custom table:
// bot.ts
import { local, dsqrDiscord, DsqrDiscordConfig } from "@dsqr/discord"
import { GatewayIntentBits, Events } from "discord.js"
import { PingPongCommand } from "./commands/ping.ts"
const config = local.getConfig()
const botConfig = {
botToken: config.discord.botToken,
clientId: config.discord.clientId,
database: { type: "sqlite", filename: config.discord.dbPath },
intents: [GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
commands: [new PingPongCommand()],
eventHandlers: {
[Events.MessageCreate]: (message) => {
if (message.author.bot) return;
const query = "INSERT INTO message_logs (guild_id, user_id, message) VALUES (?, ?, ?)";
message.client.dsqrDiscord.db.database.run(query, [
message.guildId,
message.author.id,
message.content,
]);
console.log(`Logged message from ${message.author.tag}`);
},
},
callbacks: {
onStart: () => console.log("Bot started!"),
},
} satisfies DsqrDiscordConfig
const bot = dsqrDiscord(botConfig)
bot.start()Note: This assumes a message_logs table exists (see "Extending the Database").
Note: You need GatewayIntentBits.MessageContent to read message content, which is a privileged intent that must be enabled in the Discord Developer Portal.
⇁ Extending the Database
The db object exposes getGuild, getAllGuilds, and the raw database instance, letting you run custom queries or add tables. Here's how to extend the database:
// database/sqlite.ts
import { Database, Statement } from "bun:sqlite"
export interface SqliteDatabase {
insertGuild: Statement
removeGuild: Statement
getGuild: Statement
getAllGuilds: Statement
database: Database
}
export function sqliteDatabase(filename: string = "dsqr.local.db"): SqliteDatabase {
const database = new Database(filename, { create: true })
database.exec(`
CREATE TABLE IF NOT EXISTS guilds (
guild_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
owner_id TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE TABLE IF NOT EXISTS message_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT,
user_id TEXT,
message TEXT,
timestamp INTEGER DEFAULT (strftime('%s', 'now'))
);
`)
return {
insertGuild: database.prepare("INSERT OR REPLACE INTO guilds (guild_id, name, owner_id) VALUES ($guildId, $name, $ownerId)"),
removeGuild: database.prepare("DELETE FROM guilds WHERE guild_id = $guildId"),
getGuild: database.prepare("SELECT * FROM guilds WHERE guild_id = $guildId"),
getAllGuilds: database.prepare("SELECT * FROM guilds"),
database,
}
}Use it in your bot:
bot.db.database.run("INSERT INTO message_logs (guild_id, message) VALUES (?, ?)", ["123", "Test log"]);⇁ Lifecycle Callbacks
DSQR Discord provides several callback hooks to customize how your bot responds to different lifecycle events:
callbacks: {
// When the bot starts up
onStart: () => console.log("Bot started!"),
// When bot successfully connects to Discord
onReady: (client) => console.log(`Ready as ${client.user.tag}`),
// When the bot shuts down (via bot.stop() or SIGINT)
onShutdown: () => console.log("Bot shutting down, goodbye!"),
// When any error occurs in the bot
onError: (error) => console.error("Bot error:", error.message),
// After a command executes successfully
onCommandSuccess: (interaction) => {
console.log(`Command /${interaction.commandName} used by ${interaction.user.tag}`);
},
// When a command throws an error
onCommandError: (error, interaction) => {
console.error(`Error in /${interaction.commandName}:`, error.message);
}
}All callbacks are optional - implement only the ones you need. These hooks are perfect for:
- Logging and monitoring
- Metrics collection
- User experience tracking
- Database operations
- Custom error handling
- Graceful resource cleanup
⇁ Handling Errors and Shutdowns
DSQR Discord provides automatic error handling and graceful shutdowns:
- Error handling: All errors are logged and passed to your
onErrorcallback if provided - Command errors: Command execution errors are caught and passed to your
onCommandErrorcallback - Graceful shutdown: The bot automatically handles SIGINT signals, closing the database connection and destroying the client
- Custom shutdown: You can manually shut down the bot with
bot.stop()
⇁ Nix Development Setup
DSQR Discord works well in a Nix development environment. Create a flake.nix file in your project root:
{
description = "Discord bot with Bun and Vercel AI SDK";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
bun
nodejs_22
git
];
shellHook = ''
echo "🦉🦉🦉🦉🦉🦉🦉🦉🦉🦉🦉"
'';
};
packages.default = pkgs.writeScriptBin "start-bot" ''
#!/bin/sh
bun run packages/bot/src/index.ts
'';
});
}This setup provides:
- A development shell with Bun, Node.js 22, and Git
- A default package that runs your bot with Bun
- A fun owl-filled shell greeting
To use this setup:
# Enter the development environment
nix develop
# Or run the bot directly (if you've enabled flakes)
nix run⇁ Tips for Success
- Start with the minimal configuration (token, client ID, database) and add features as needed
- Use TypeScript's
satisfieskeyword withDsqrDiscordConfigto get type checking - Add only the intents your bot needs for better security
- Implement lifecycle callbacks that make sense for your use case
- Access the Discord.js client directly via
bot.clientwhen needed - Use the built-in database for simple persistence needs
- Consider using environment variables with the built-in Zod validation
- Remember that your existing code will continue to work as the library is backward compatible
