npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@quadslab.io/discord-bot

v1.0.0

Published

A modular Discord bot framework with dynamic module loading and runtime migrations

Downloads

99

Readme

QuadsLabBot

A modular Discord bot framework built with TypeScript and Discord.js. Features a plugin architecture with dynamic module loading, runtime database migrations, AI integration, and an inter-module event bus. Build new features as self-contained modules without touching the core.

Features

  • Drop-In Module System — Auto-discovered modules with their own commands, events, services, database migrations, settings, and leaderboards
  • Slash Command Framework — Declarative command builder with permission guards, subcommands, autocomplete, and context menus
  • Inter-Module Event Bus — Loosely coupled communication between modules (e.g., voice tracking emits session events that the points module listens to)
  • Per-Module Database Migrations — Each module manages its own SQL migrations that run automatically on load
  • Centralized Settings — Modules register configurable settings that server admins manage through /settings
  • Leaderboard Registry — Any module can register leaderboard providers, all accessible through a unified /leaderboard command
  • Cron Scheduler — Built-in scheduled task runner with per-module job registration
  • AI Provider Abstraction — Pluggable AI backends (Claude, OpenAI) for modules that need AI features
  • Docker Ready — Multi-stage Docker build with MySQL service, health checks, and compose orchestration

Quick Start

Prerequisites

Installation

# Clone the repository
git clone https://github.com/HardHeadHackerHead/discord-bot.git
cd QuadsLabBot

# Install dependencies
npm install

# Set up environment
cp .env.example .env
# Edit .env with your bot token and database credentials

# Generate Prisma client
npm run db:generate

# Push database schema
npm run db:push

# Start in development mode
npm run dev

Docker

# Start with Docker Compose (bot + MySQL)
docker compose up -d

# View logs
docker compose logs -f bot

Configuration

Copy .env.example to .env and fill in the required values:

| Variable | Required | Description | |---|---|---| | BOT_TOKEN | Yes | Discord bot token | | CLIENT_ID | Yes | Discord application client ID | | DATABASE_URL | Yes | MySQL connection string (mysql://user:pass@host:3306/dbname) | | DEV_GUILD_ID | No | Guild ID for instant slash command updates during development | | NODE_ENV | No | development or production (default: development) | | LOG_LEVEL | No | debug, info, warn, or error (default: debug) |

See .env.example for the full list of configuration options including website integration and ngrok tunnel settings.

Included Modules

These modules ship with the framework and can be enabled/disabled per server:

| Module | Description | Commands | |---|---|---| | admin | Module management, settings, bot stats | /modules, /reload, /settings, /lines, /clear | | help | Lists all available commands | /commands | | user-tracking | Tracks users joining/leaving guilds | /userinfo | | message-tracking | Tracks message counts with spam cooldown | /messages | | voice-tracking | Tracks voice channel time with session management | /voicetime | | points | Currency system with balance and transaction history | /points | | leaderboard | Unified leaderboard display for all module stats | /leaderboard | | polls | Create and manage polls with voting | /poll | | role-management | Self-assignable roles via dropdown menus | /roles | | message-editor | Edit bot messages via emoji reactions | (reaction-based) |

How Modules Work Together

Modules communicate through events, not direct imports. This means any module can be removed without breaking others:

message-tracking ──emits──> "message-counted" ──listened by──> points
voice-tracking   ──emits──> "session-ended"   ──listened by──> points
points           ──registers──> leaderboard provider ──displayed by──> leaderboard

If points isn't loaded, message-tracking and voice-tracking still work — they just track stats without awarding points.

Creating Modules

Module Structure

Every module lives in its own directory under src/modules/:

src/modules/my-module/
├── index.ts              # Exports module INSTANCE (not class)
├── module.ts             # Module class with metadata and lifecycle
├── LICENSE               # Module-specific license (optional)
├── migrations/           # SQL migrations (auto-run on load)
│   └── 001_initial.sql
├── commands/             # Slash commands
│   └── my-command.ts
├── events/               # Discord event handlers
│   └── interactionCreate.ts
├── services/             # Business logic and database operations
│   └── MyService.ts
└── components/           # UI builders (embeds, buttons, modals)
    └── MyPanel.ts

Minimal Module Example

module.ts — Define metadata and lifecycle:

import { BaseModule, ModuleMetadata, ModuleContext } from '../../types/module.types.js';
import { command as pingCommand } from './commands/ping.js';
import { Logger } from '../../shared/utils/logger.js';

const logger = new Logger('Ping');

export class PingModule extends BaseModule {
  readonly metadata: ModuleMetadata = {
    id: 'ping',
    name: 'Ping',
    description: 'Simple ping/pong command',
    version: '1.0.0',
    author: 'Your Name',
    isCore: false,
    isPublic: true,
    dependencies: [],
    priority: 50,
  };

  // Set to null if no database tables needed
  readonly migrationsPath = null;

  constructor() {
    super();
    this.commands = [pingCommand];
  }

  async onLoad(context: ModuleContext): Promise<void> {
    await super.onLoad(context);
    logger.info('Ping module loaded');
  }
}

index.ts — Export an instance (not the class):

import { PingModule } from './module.js';
export default new PingModule();

commands/ping.ts — Define a slash command:

import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
import { SlashCommand } from '../../../types/command.types.js';

export const command: SlashCommand = {
  type: 'slash',
  data: new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Check bot latency') as SlashCommandBuilder,

  async execute(interaction: ChatInputCommandInteraction): Promise<void> {
    const latency = Date.now() - interaction.createdTimestamp;
    await interaction.reply(`Pong! ${latency}ms`);
  },
};

Drop this folder into src/modules/ping/, restart the bot, and the /ping command is live. No registration code, no config files — the framework discovers it automatically.

Full Development Guide

See docs/MODULE_DEVELOPMENT.md for the complete guide covering:

  • Database migrations and the DatabaseService API
  • Commands with subcommands, autocomplete, and context menus
  • Event handlers and custom ID conventions
  • Services with dependency injection pattern
  • UI components (embeds, buttons, select menus, modals)
  • Inter-module event bus (emitting and subscribing)
  • Centralized settings system (per-guild configuration)
  • Leaderboard registry (registering stat providers)
  • Cron jobs (scheduled tasks)
  • Panel state management for multi-step UI flows

Building Modules with Claude Code

This project is designed to work well with Claude Code. The module architecture follows strict, consistent patterns that Claude Code can replicate reliably. Here's how to use it effectively.

Creating a New Module

Give Claude Code context about what you want, and reference the existing patterns:

Read docs/MODULE_DEVELOPMENT.md and the points module in src/modules/points/
as a reference. Then create a new module called "warnings" that:

- Tracks user warnings with reasons
- /warn command (admin only) to warn a user
- /warnings command to view a user's warning history
- Stores warnings in a database table
- Emits a "warning-issued" event so other modules can react

Claude Code will follow the established patterns because:

  1. The module structure is enforced (every module has the same file layout)
  2. The types are well-documented (ModuleMetadata, SlashCommand, BaseModule)
  3. Real examples exist in the codebase to reference

Tips for Effective Prompts

Be specific about the module's behavior:

Create a "birthday" module that lets users set their birthday with /birthday set,
shows upcoming birthdays with /birthday list, and announces birthdays daily
using the cron system. Store dates in a birthday_dates table.

Reference existing modules for complex patterns:

Look at how the points module subscribes to events from voice-tracking
and message-tracking. Create a "levels" module that listens to the same
events but calculates XP and levels instead of points. Register a
leaderboard showing level rankings.

Ask for specific integrations:

Create a "welcome" module that sends a welcome embed when users join.
Register a setting for the welcome channel ID and welcome message text
using the centralized settings system so admins can configure it with /settings.

What Claude Code Should Know

When creating modules, these are the key patterns:

| Pattern | How It Works | |---|---| | Service injection | Services are created in onLoad(), then passed to commands/events via setService() functions | | Custom IDs | All interaction custom IDs use <module>:<action>:<data> format (e.g., polls:vote:abc123) | | Table naming | Database tables are prefixed with the module ID (e.g., points_user_points) | | Event naming | Module events use <module-id>:<event-name> format (e.g., voice-tracking:session-ended) | | Exports | index.ts must export an instance: export default new MyModule() | | Migrations | Numbered SQL files in migrations/ (e.g., 001_initial.sql) — run automatically | | Cleanup | Always unsubscribe events, unregister leaderboards/settings/cron in onUnload() |

Example Prompts for Common Module Types

Tracking module (like message-tracking):

Create a "reaction-tracking" module that counts reactions users give and receive.
Track both given and received counts. Emit events when reactions are counted.
Register two leaderboards: "Most Reactions Given" and "Most Reactions Received".

Admin module (like role-management):

Create an "auto-role" module with an admin command /autorole that configures
roles automatically assigned when users join. Support multiple roles per guild.
Use the settings system for an optional "delay" before role assignment.

Game module (like polls):

Create a "trivia" module with /trivia that posts a question with 4 button
options. Use a panel component for the question embed and answer buttons.
Track correct answers per user and register a leaderboard.

Integration module (with events):

Create a "milestones" module that listens to the points:awarded event.
When a user crosses a milestone threshold (100, 500, 1000, 5000 points),
send a congratulations embed to a configurable channel. Use the settings
system for the announcement channel.

Project Structure

QuadsLabBot/
├── src/
│   ├── index.ts                # Entry point
│   ├── bot.ts                  # Bot initialization and orchestration
│   ├── config/
│   │   └── environment.ts      # Env validation with zod
│   ├── core/                   # Core framework (don't modify for modules)
│   │   ├── ai/                 # AI provider abstraction (Claude, OpenAI)
│   │   ├── client/             # Discord.js client wrapper
│   │   ├── commands/           # Command registration and routing
│   │   ├── cron/               # Scheduled task runner
│   │   ├── database/           # Prisma client + migration runner
│   │   ├── events/             # Event handler system
│   │   ├── leaderboards/       # Leaderboard registry
│   │   ├── modules/            # Module loader, registry, event bus
│   │   └── settings/           # Per-module settings system
│   ├── modules/                # Plugin modules (add yours here)
│   ├── shared/
│   │   └── utils/              # Logger, embed helpers, pagination, permissions
│   └── types/                  # TypeScript types for modules, commands, events
├── prisma/                     # Prisma schema and core migrations
├── docs/                       # Documentation
├── Dockerfile                  # Multi-stage Docker build
└── docker-compose.yml          # Docker Compose (bot + MySQL)

Scripts

| Command | Description | |---|---| | npm run dev | Start in development mode with hot reload | | npm run build | Compile TypeScript to JavaScript | | npm start | Run the compiled bot | | npm run db:generate | Generate Prisma client | | npm run db:migrate | Run Prisma migrations | | npm run db:push | Push schema changes to database | | npm run db:studio | Open Prisma Studio (database GUI) | | npm run lint | Run ESLint | | npm run lint:fix | Run ESLint with auto-fix |

Contributing

See CONTRIBUTING.md for guidelines on how to contribute.

License

The core framework is licensed under the MIT License.

Individual modules in src/modules/ may have their own licenses. Check each module's LICENSE file for details. Modules without a license file fall under the project's MIT license.