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

@scotthamilton77/discord-bot-lib

v0.1.0

Published

TypeScript library for managing Discord bot identities, messaging, and multi-bot coordination

Readme

@scotthamilton77/discord-bot-lib

TypeScript library for managing Discord bot identities, messaging, and multi-bot coordination. Wraps discord.js with a clean API surface: factory-based construction, event-driven callbacks, async iterables, and built-in message chunking.

Features

  • Factory construction -- Bot.fromConfig() connects, verifies guilds, and returns a ready bot in one call
  • Dual receive API -- event-driven callbacks (onMention, onReply, onMessage) and async iterables (mentions(), replies(), messages())
  • Multi-bot management -- ConnectorManager aggregates mentions across bots with lifecycle events
  • Guided onboarding -- BotOnboarding walks through token validation, server invitation, and permission verification
  • Message chunking -- automatically splits messages at paragraph/line/word boundaries to stay within Discord's 2000-char limit
  • File attachments -- send files from Buffer or file path, with size validation and filename sanitization
  • History pagination -- fetchHistory() async generator pages through channel history with automatic rate-limit retry
  • Dual build -- ships ESM + CJS + type declarations via tsup

Installation

npm install @scotthamilton77/discord-bot-lib

discord.js is a direct dependency -- no separate peer install needed.

Quick Start

import { Bot } from "@scotthamilton77/discord-bot-lib";

const bot = await Bot.fromConfig({
  id: "my-bot",
  name: "GreeterBot",
  token: process.env.DISCORD_TOKEN!,
});

// Respond to @mentions
bot.onMention(async (event) => {
  await bot.send(event.channelId, `Hello, ${event.author.username}!`);
});

// Respond to replies to messages this bot sent
bot.onReply(async (event, original) => {
  await bot.send(event.channelId, `You replied to my message ${original.messageId}`);
});

// Listen to all messages in a specific channel
bot.onMessage("123456789012345678", (event) => {
  console.log(`${event.author.username}: ${event.content}`);
});

// Error handling
bot.on("error", (err) => console.error("Bot error:", err.message));

Async Iterables

Every event type is also available as an AsyncIterable, useful for sequential processing with for await:

import { Bot } from "@scotthamilton77/discord-bot-lib";

const bot = await Bot.fromConfig({
  id: "iter-bot",
  name: "IterBot",
  token: process.env.DISCORD_TOKEN!,
});

// Process mentions one at a time
for await (const event of bot.mentions()) {
  await bot.reply(event.channelId, event.messageId, "Got it!");
}
// Filter channel messages with a predicate
for await (const event of bot.messages("123456789012345678", {
  filter: (msg) => msg.content.startsWith("!cmd"),
})) {
  await bot.send(event.channelId, `Command received: ${event.content}`);
}

Multi-Bot Management

ConnectorManager registers multiple bots and aggregates their events:

import { Bot, ConnectorManager } from "@scotthamilton77/discord-bot-lib";

const manager = new ConnectorManager();

const botA = await Bot.fromConfig({ id: "a", name: "Alpha", token: TOKEN_A });
const botB = await Bot.fromConfig({ id: "b", name: "Bravo", token: TOKEN_B });

manager.addBot(botA);
manager.addBot(botB);

// Single handler receives mentions from all bots
manager.onMention((event, bot) => {
  console.log(`${bot.name} was mentioned by ${event.author.username}`);
});

// Lifecycle events
manager.on("botError", (bot, error) => {
  console.error(`Error from ${(bot as { name: string }).name}:`, error);
});

// Status overview
console.log(manager.status());
// [{ id: "a", name: "Alpha", status: "ready" }, ...]

// Clean shutdown
await manager.shutdown();

Bot Onboarding

BotOnboarding provides a guided 3-step setup flow -- useful for interactive CLIs or admin panels:

import { BotOnboarding } from "@scotthamilton77/discord-bot-lib";

const onboarding = new BotOnboarding("new-bot", "NewBot");

// Steps: provide_token -> invite_to_server -> verify_permissions
for (const step of onboarding.steps) {
  console.log(`${step.id}: ${step.label} [${step.status}]`);
  console.log(`  ${step.instructions}`);
}

// Complete steps in order
const tokenResult = await onboarding.steps[0]!.complete(process.env.DISCORD_TOKEN!);
if (!tokenResult.success) throw new Error(tokenResult.error);

const inviteResult = await onboarding.steps[1]!.complete();
if (!inviteResult.success) throw new Error(inviteResult.error);

const verifyResult = await onboarding.steps[2]!.complete();
if (!verifyResult.success) throw new Error(verifyResult.error);

// Ready bot is available after successful onboarding
const bot = onboarding.bot!;
console.log(`${bot.name} is ${bot.status}`);

Sending Messages

Messages can be plain strings, or objects with optional embeds and file attachments:

// Plain text (auto-chunked if over 2000 chars)
await bot.send(channelId, "Hello, world!");

// With embeds
await bot.send(channelId, {
  content: "Check this out:",
  embeds: [{ title: "My Embed", description: "Some rich content" }],
});

// With file attachments (Buffer or absolute file path)
await bot.send(channelId, {
  content: "Here's the report:",
  files: [
    { name: "report.csv", data: Buffer.from("a,b,c\n1,2,3") },
    { name: "chart.png", data: "/absolute/path/to/chart.png" },
  ],
});

// Reply to a specific message
await bot.reply(channelId, messageId, "Thanks for your message!");

// Direct message
await bot.sendDM(userId, "Private hello!");

// Reactions
await bot.react(channelId, messageId, "\ud83d\udc4d");

// Edit a message
await bot.editMessage(channelId, messageId, "Updated content");

Fetching History

// Fetch recent messages (up to 100)
const recent = await bot.fetchMessages(channelId, 50);

// Page through history with an async generator
for await (const page of bot.fetchHistory(channelId, { after: lastSeenId, limit: 1000 })) {
  for (const msg of page) {
    console.log(`${msg.author.username}: ${msg.content}`);
  }
}

// Fetch backwards from a message
for await (const page of bot.fetchHistory(channelId, { before: messageId, limit: 500 })) {
  // pages arrive in reverse chronological order
}

// Get attachments for a specific message
const attachments = await bot.getMessageAttachments(channelId, messageId);

Message Utilities

import {
  chunkMessage,
  DISCORD_MAX_MESSAGE_LENGTH,
  validateAttachmentSize,
  sanitizeAttachmentName,
  downloadAttachment,
  MAX_ATTACHMENT_BYTES,
} from "@scotthamilton77/discord-bot-lib";

// Split long text into Discord-safe chunks
const chunks = chunkMessage(longText);
// Splits at paragraph -> line -> word -> hard-cut boundaries

// Validate attachment size (throws if > 25 MB)
validateAttachmentSize({ size: file.size, name: file.name, id: file.id });

// Sanitize filenames (replaces unsafe chars with underscores)
const safeName = sanitizeAttachmentName({ name: "report[final].csv", id: "123" });
// "report_final_.csv"

// Download a Discord attachment to a buffer
const { buffer, filename, contentType } = await downloadAttachment(attachment);

API Reference

Classes

| Export | Description | |---|---| | Bot | Single bot identity. Created via Bot.fromConfig(config). Handles sending, receiving, and channel operations. | | ConnectorManager | Multi-bot registry with aggregated mention handling and lifecycle events. | | BotOnboarding | Guided 3-step setup: token validation, server invite, permission check. | | EventBuffer<T> | Single-consumer AsyncIterable<T> bridge between push events and for await loops. |

Functions

| Export | Description | |---|---| | chunkMessage(text, limit?) | Split text into chunks within Discord's 2000-char limit. | | validateAttachmentSize(attachment) | Throws if attachment exceeds 25 MB. | | sanitizeAttachmentName(attachment) | Replace unsafe filename characters with underscores. | | downloadAttachment(attachment) | Download a Discord attachment to a Buffer. |

Constants

| Export | Value | |---|---| | DISCORD_MAX_MESSAGE_LENGTH | 2000 | | MAX_ATTACHMENT_BYTES | 26214400 (25 MB) | | DEFAULT_SENT_MESSAGE_CACHE_SIZE | 1000 |

Key Types

| Type | Description | |---|---| | BotConfig | Configuration for Bot.fromConfig() -- id, name, token, optional intents/channels/cacheSize. | | BotStatus | "unregistered" \| "configuring" \| "connecting" \| "verifying" \| "ready" \| "disconnected" \| "failed" | | MessageEvent | Incoming message with author, content, channelId, mentions, and raw discord.js Message. | | SentMessage | Tracked outgoing message with messageId, channelId, timestamp, and raw escape hatch. | | MessageContent | string \| { content?: string; embeds?: unknown[]; files?: FileAttachment[] } | | FetchedMessage | Lightweight message from fetchMessages() / fetchHistory(). | | FetchHistoryOptions | Discriminated union: { after: string } \| { before: string } with optional limit. | | MessageFilter | (message: MessageEvent) => boolean predicate for onMessage() / messages(). | | FileAttachment | { data: Buffer \| string; name: string } for sending files. | | OnboardingStep | Step in the onboarding flow with id, label, instructions, status, and complete(). | | AttachmentLike | Minimal shape for attachment validation utilities. | | DownloadableAttachment | Extends AttachmentLike with url and contentType for downloading. | | DownloadedAttachment | Result of downloadAttachment(): buffer, filename, contentType. | | ManagerEvents | Lifecycle event signatures for ConnectorManager. |

Requirements

  • Node.js >= 20
  • TypeScript >= 5.5 (for development)
  • discord.js ^14.25 (included as a dependency)

License

ISC