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

@m-programmation/discord-giveaways

v0.2.0

Published

A powerful, extensible Discord giveaways module with fully customizable embeds and complete history tracking.

Readme

@m-programmation/discord-giveaways

npm version npm downloads license

A discord.js module for running advanced giveaways: fully customizable embeds, reaction- or button-based entries, automatic ending, and a pluggable storage architecture so you can keep a complete history of every giveaway.

🚧 The module is currently at its base version (0.x): the core (create/end/reroll a giveaway, customizable embeds, storage providers) is functional, but the API may still evolve before 1.0. Feedback and issues are welcome!

Features

  • ✅ Create, end and reroll giveaways
  • start / end / reroll embeds are fully customizable
  • ✅ Automatic ending when a giveaway expires
  • ✅ Reaction-based or native button-based entries (configurable), with join/leave tracking
  • ✅ Optional canJoin gate + custom denial handler, e.g. to restrict entries to certain roles
  • ✅ 100% TypeScript, typed events
  • ✅ Built-in SQL storage (SqlProvider): SQLite, MySQL, MariaDB, Postgres — connected, verified and cached automatically on startup
  • ✅ Pluggable storage (GiveawaysProvider) so you can plug in any other database and keep a complete history
  • 🔜 Advanced history/queries, ready-to-use commands

Installation

npm install @m-programmation/discord-giveaways discord.js

discord.js (^14.14.0) is a peer dependency: install it yourself and provide your own Client.

Quick start

import { Client, GatewayIntentBits } from "discord.js";
import { GiveawaysManager } from "@m-programmation/discord-giveaways";

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessageReactions],
});

const giveaways = new GiveawaysManager(client);

client.once("ready", async () => {
  // Loads existing giveaways and starts the automatic end-checking loop
  await giveaways.init();
});

// Start a giveaway
await giveaways.start({
  channelId: "123456789012345678",
  prize: "Discord Nitro",
  winnerCount: 1,
  duration: 60 * 60 * 1000, // 1 hour
  hostedBy: "987654321098765432",
});

// Listen to events
giveaways.on("giveawayEnd", (giveaway, winners) => {
  console.log(`Giveaway "${giveaway.prize}" ended, winners:`, winners);
});

API

GiveawaysManager

| Method | Description | | ----------------------------- | ------------------------------------------------------------- | | init() | Loads giveaways from the provider, starts the auto-end loop | | start(options) | Creates and posts a new giveaway | | end(messageId) | Ends a giveaway and draws the winners | | reroll(messageId, options?) | Draws one or more new winners | | delete(messageId) | Removes a giveaway from the provider and the cache | | get(messageId) | Retrieves a giveaway by its message ID | | getAll() | All known giveaways (active and ended) | | stop() | Stops the auto-end loop and releases the provider's resources |

Events

giveawayStart, giveawayEnd, giveawayReroll, giveawayDelete — all typed, with the Giveaway (and the winners, where relevant) as arguments.

Customizing embeds

Each embed (start, end, reroll) can be replaced individually:

import { EmbedBuilder } from "discord.js";
import { GiveawaysManager } from "@m-programmation/discord-giveaways";

const giveaways = new GiveawaysManager(client, {
  embeds: {
    start: (giveaway) => new EmbedBuilder().setTitle(`🎁 ${giveaway.prize}`).setColor("Random"),
  },
});

Button-based entries

By default the manager reacts to the giveaway message with reaction (👍 the original behaviour, unchanged). Set entryMode: "button" to post a native join/leave button instead — no reaction is added, and participants are tracked in Giveaway.participants (persisted through the provider on every click) rather than read from message reactions:

import { GiveawaysManager } from "@m-programmation/discord-giveaways";

const giveaways = new GiveawaysManager(client, {
  entryMode: "button",
  // Optional: gate entries (e.g. required roles) — return false to deny.
  canJoin: (giveaway, userId) => {
    const requiredRoles = giveaway.extra.requiredRoles as string[] | undefined;
    if (!requiredRoles?.length) return true;
    const member = client.guilds.cache.get(giveaway.guildId)?.members.cache.get(userId);
    return requiredRoles.some((roleId) => member?.roles.cache.has(roleId));
  },
  // Optional: fully custom denial reply instead of the default text.
  onJoinDenied: async (interaction, giveaway) => {
    await interaction.reply({ content: `You're missing a required role for "${giveaway.prize}".`, ephemeral: true });
  },
  // Optional: override the built-in ephemeral texts / button label ({count} = participant count).
  entryMessages: {
    joined: "🎉 You're in!",
    left: "You left the giveaway.",
    buttonLabel: "Enter ({count})",
  },
});

Clicking the button toggles membership (click again to leave), the button label updates live with the participant count, and it's automatically disabled once the giveaway ends. canJoin and onJoinDenied are only used in "button" mode — reaction-based giveaways have no entry gate (Discord doesn't let a bot restrict who can react).

Persistence & history

By default, GiveawaysManager uses a MemoryProvider (in-memory, not persistent — handy for testing, but everything is lost on restart).

Built-in SQL provider (SQLite, MySQL, MariaDB, Postgres)

SqlProvider covers all four out of the box. Pick a dialect and pass it to the manager — on giveaways.init() it opens the connection, runs a clean connectivity check (a SELECT 1, wrapped in a clear GiveawayError if it fails), creates the giveaways table if it doesn't exist yet, and warms up an in-memory cache so reads never hit the database again:

import { GiveawaysManager, SqlProvider } from "@m-programmation/discord-giveaways";

// SQLite (local file)
const provider = new SqlProvider({ dialect: "sqlite", filename: "./data/giveaways.sqlite" });

// MySQL / MariaDB
// const provider = new SqlProvider({
//   dialect: "mysql", // or "mariadb"
//   host: "localhost",
//   port: 3306,
//   user: "bot",
//   password: "secret",
//   database: "giveaways",
// });

// Postgres
// const provider = new SqlProvider({
//   dialect: "postgres",
//   host: "localhost",
//   port: 5432,
//   user: "bot",
//   password: "secret",
//   database: "giveaways",
// });

const giveaways = new GiveawaysManager(client, { provider });

client.once("ready", async () => {
  await giveaways.init(); // connects, checks the connection, migrates, warms the cache
});

SqlProvider is built on Knex and only requires the driver matching your dialect as a peer dependency: better-sqlite3 for SQLite, mysql2 for MySQL/MariaDB, pg for Postgres.

npm install better-sqlite3   # sqlite
npm install mysql2           # mysql / mariadb
npm install pg               # postgres

Call await giveaways.stop() on shutdown to close the underlying connection cleanly.

Custom providers

To plug in anything else (a different ORM, Redis, a REST backend, ...), implement the GiveawaysProvider interface and pass it to the manager:

import type { GiveawaysProvider } from "@m-programmation/discord-giveaways";

const provider: GiveawaysProvider = {/* init?, getAll, create, update, delete, dispose? */};

const giveaways = new GiveawaysManager(client, { provider });

giveaways.getAll() returns every giveaway known to the manager (active and ended) — the foundation to build a full history view on top of.

Contributing / reporting a bug

Source code, issues and discussions live on GitHub.

License

Apache-2.0