Skip to main content
Featured image for project: Navi: Discord Bot

Navi: Discord Bot

In Progress
discord typescript

Navi Bot

“Hey! Listen!” - Navi

A plugin-based Discord bot built with Discord.js, TypeScript, and Bun. Blazing fast, super extensible, and sanely built. Navi Bot provides your server with all the features a Discord guild needs, and with some they don’t! Missing a feature? Check out the plugin development guide!

Getting Started

# Install dependencies
bun install

# Copy environment file and fill in your tokens
cp .env.example .env

# Run in development (with hot reload)
bun run dev

# Run in production
bun run start

Project Structure

navi-bot/
├── src/
│   ├── core/           # Bot framework
│   │   ├── bot.ts      # Main bot class
│   │   ├── plugin-loader.ts  # Plugin discovery & loading
│   │   ├── config.ts   # YAML config management
│   │   ├── database.ts # MongoDB setup
│   │   └── logger.ts   # Logging utility
│   ├── types/          # TypeScript definitions
│   └── index.ts        # Entry point
├── plugins/            # Plugin directory
│   ├── ping/
│   │   └── plugin.ts
│   └── economy/
│       └── plugin.ts
├── config/             # Auto-generated YAML configs (per plugin)
└── data/               # MongoDB data

Creating a Plugin

NOTE!
For more in-depth guide, see
PLUGIN_DEVELOPMENT.md for a detailed overview on plugin creation

Create a new folder in plugins/ with a plugin.ts file:

import { SlashCommandBuilder } from "discord.js";
import { z } from "zod";
import type { Plugin, PluginContext, Command } from "../../src/types";

// Optional: Define a config schema (auto-generates YAML)
const configSchema = z.object({
  someOption: z.string().default("default value"),
  enabled: z.boolean().default(true),
});

type MyConfig = z.infer<typeof configSchema>;

const plugin: Plugin<typeof configSchema> = {
  manifest: {
    name: "my-plugin",
    version: "1.0.0",
    description: "What this plugin does",
    author: "Your Name",
    dependencies: {
      hard: [],  // Required plugins (fails if missing)
      soft: [],  // Optional plugins (loads first if present)
    },
  },

  config: {
    schema: configSchema,
    defaults: {
      someOption: "default value",
      enabled: true,
    },
  },

  async onLoad(ctx: PluginContext<MyConfig>) {
    // Register commands
    ctx.registerCommand({
      data: new SlashCommandBuilder()
        .setName("mycommand")
        .setDescription("Does something"),
      
      async execute(interaction, ctx) {
        await interaction.reply(`Config value: ${ctx.config.someOption}`);
      },
    });

    // Register events
    ctx.registerEvent({
      name: "messageCreate",
      async execute(ctx, message) {
        // Handle event
      },
    });

    ctx.logger.info("Plugin loaded!");
  },

  // Optional: cleanup on unload
  async onUnload() {
    // Cleanup resources
  },
};

export default plugin;

Plugin Context

Every plugin receives a context object with:

PropertyDescription
clientDiscord.js Client instance
loggerPrefixed logger (info, warn, error, debug)
configParsed & validated config from YAML
dbMongoDB database instance
dbPrefixCollection prefix for this plugin (e.g., economy_)
registerCommand(cmd)Register a slash command
registerEvent(event)Register an event handler
getPlugin(name)Get another loaded plugin for cross-plugin communication

Database Usage

Plugins use MongoDB for data storage. Get the core-utils API to access database helpers:

const coreUtils = ctx.getPlugin<{ api: CoreUtilsAPI }>("core-utils");
const api = coreUtils?.api;

// Get a MongoDB collection (automatically created)
const collection = api.database.getCollection<MyType>(ctx, 'my_collection');

// Create indexes for performance
collection.createIndex({ user_id: 1 }, { unique: true }).catch(() => {});

// Use the collection
const items = await collection.find({ user_id: userId }).toArray();
await collection.insertOne({ user_id: userId, data: "value" });

Configuration

Plugin configs are auto-generated as YAML files in config/:

# config/my-plugin.yaml
someOption: "default value"
enabled: true

Edit the YAML to change settings. Invalid configs fall back to defaults.

Dependencies

Plugins can declare dependencies on other plugins:

dependencies: {
  hard: ["required-plugin"],  // Bot fails to start if missing
  soft: ["optional-plugin"],  // Loads first if present, ignored if missing
}

Cross-Plugin Communication

Access other plugins via ctx.getPlugin():

const economy = ctx.getPlugin<EconomyPlugin>("economy");
if (economy) {
  // Use economy plugin API
}