Androz2091 / discord-giveaways

πŸŽ‰ Complete framework to facilitate the creation of giveaways using discord.js
https://discord-giveaways.js.org
MIT License
336 stars 127 forks source link
customizable discord discord-giveaway discordbot embeds framework multi-language-support

Discord Giveaways

discordBadge downloadsBadge versionBadge documentationBadge

Discord Giveaways is a powerful Node.js module that allows you to easily create giveaways!

Features

Installation

npm install --save discord-giveaways

Examples

You can read this example bot on GitHub: discord-giveaways-bot

Launch of the module

Required Discord Intents: Guilds and GuildMessageReactions.
Optional Discord Privileged Intent for better performance: GuildMembers.

const Discord = require('discord.js');
const client = new Discord.Client({
    intents: [
        Discord.IntentsBitField.Flags.Guilds,
        Discord.IntentsBitField.Flags.GuildMessageReactions,
        Discord.IntentsBitField.Flags.GuildMembers // Optional, for better performance
    ]
});

// Requires Manager from discord-giveaways
const { GiveawaysManager } = require('discord-giveaways');
const manager = new GiveawaysManager(client, {
    storage: './giveaways.json',
    default: {
        botsCanWin: false,
        embedColor: '#FF0000',
        embedColorEnd: '#000000',
        reaction: 'πŸŽ‰'
    }
});
// We now have a giveawaysManager property to access the manager everywhere!
client.giveawaysManager = manager;

client.on('ready', () => {
    console.log('Bot is ready!');
});

client.login(process.env.DISCORD_BOT_TOKEN);

After that, giveaways that are not yet completed will start to be updated again and new giveaways can be started. You can pass an options object to customize the giveaways. Here is a list of them:

Start a giveaway

client.on('interactionCreate', (interaction) => {
    const ms = require('ms');

    if (interaction.isChatInputCommand() && interaction.commandName === 'start') {
        // /start 2d 1 Awesome prize!
        // Will create a giveaway with a duration of two days, with one winner and the prize will be "Awesome prize!"

        const duration = interaction.options.getString('duration');
        const winnerCount = interaction.options.getInteger('winners');
        const prize = interaction.options.getString('prize');

        client.giveawaysManager
            .start(interaction.channel, {
                duration: ms(duration),
                winnerCount,
                prize
            })
            .then((data) => {
                console.log(data); // {...} (messageId, end date and more)
            });
        // And the giveaway has started!
    }
});

This allows you to start a new giveaway. Once the start() function is called, the giveaway starts, and you only have to observe the result, the package does the rest!

⚠ ATTENTION!

The command examples below (reroll, edit delete, end) can be executed on any server your bot is a member of if a person has the prize or the messageId of a giveaway. To prevent abuse we recommend to check if the prize or the messageId that was provided by the command user is for a giveaway on the same server, if it is not, then cancel the command execution.

const query = interaction.options.getString('query');
const giveaway =
    // Search with giveaway prize
    client.giveawaysManager.giveaways.find((g) => g.guildId === interaction.guildId && g.prize === query) ||
    // Search with messageId
    client.giveawaysManager.giveaways.find((g) => g.guildId === interaction.guildId && g.messageId === query);

// If no giveaway was found
if (!giveaway) return interaction.reply(`Unable to find a giveaway for \`${query}\`.`);

Reroll a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'reroll') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .reroll(messageId)
            .then(() => {
                interaction.reply('Success! Giveaway rerolled!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

Edit a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'edit') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .edit(messageId, {
                addTime: 5000,
                newWinnerCount: 3,
                newPrize: 'New Prize!'
            })
            .then(() => {
                interaction.reply('Success! Giveaway updated!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

Note: to reduce giveaway duration, define addTime with a negative number! For example addTime: -5000 will reduce giveaway duration by 5 seconds!

Delete a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'delete') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .delete(messageId)
            .then(() => {
                interaction.reply('Success! Giveaway deleted!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

⚠️ Note: when you use the delete function, the giveaway data and the message of the giveaway are deleted (by default). You cannot restore a giveaway once you have deleted it!

End a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'end') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .end(messageId)
            .then(() => {
                interaction.reply('Success! Giveaway ended!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

Pause a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'pause') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .pause(messageId)
            .then(() => {
                interaction.reply('Success! Giveaway paused!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

⚠️ Note: the pause function overwrites/edits the pauseOptions object property of a giveaway!

Unpause a giveaway

client.on('interactionCreate', (interaction) => {
    if (interaction.isChatInputCommand() && interaction.commandName === 'unpause') {
        const messageId = interaction.options.getString('message_id');
        client.giveawaysManager
            .unpause(messageId)
            .then(() => {
                interaction.reply('Success! Giveaway unpaused!');
            })
            .catch((err) => {
                interaction.reply(`An error has occurred, please check and try again.\n\`${err}\``);
            });
    }
});

Fetch giveaways

// A list of all the giveaways
const allGiveaways = client.giveawaysManager.giveaways; // [ {Giveaway}, {Giveaway} ]

// A list of all the giveaways on the server with Id "1909282092"
const onServer = client.giveawaysManager.giveaways.filter((g) => g.guildId === '1909282092');

// A list of the current active giveaways (not ended)
const notEnded = client.giveawaysManager.giveaways.filter((g) => !g.ended);

Exempt Members

Function to filter members. If true is returned, the member will not be able to win the giveaway.

client.giveawaysManager.start(interaction.channel, {
    duration: 60000,
    winnerCount: 1,
    prize: 'Free Steam Key',
    // Only members who have the "Nitro Boost" role are able to win
    exemptMembers: (member, giveaway) => !member.roles.cache.some((r) => r.name === 'Nitro Boost')
});

Note (only for proficients): if you want to use values of global variables inside of the function without using giveaway.extraData, you can use the Function constructor:

const roleName = 'Nitro Boost';

client.giveawaysManager.start(interaction.channel, {
    duration: 60000,
    winnerCount: 1,
    prize: 'Free Steam Key',
    // Only members who have the the role which is assigned to "roleName" are able to win
    exemptMembers: new Function(
        'member',
        'giveaway',
        `return !member.roles.cache.some((r) => r.name === '${roleName}')`
    )
});

⚠ Note

Last Chance

client.giveawaysManager.start(interaction.channel, {
    duration: 60000,
    winnerCount: 1,
    prize: 'Discord Nitro!',
    lastChance: {
        enabled: true,
        content: '⚠️ **LAST CHANCE TO ENTER !** ⚠️',
        threshold: 10_000,
        embedColor: '#FF0000'
    }
});

Pause Options

client.giveawaysManager.start(interaction.channel, {
    duration: 60000,
    winnerCount: 1,
    prize: 'Discord Nitro!',
    pauseOptions: {
        isPaused: true,
        content: '⚠️ **THIS GIVEAWAY IS PAUSED !** ⚠️',
        unpauseAfter: null,
        embedColor: '#FFFF00',
        infiniteDurationText: '`NEVER`'
    }
});

Bonus Entries

client.giveawaysManager.start(interaction.channel, {
    duration: 60000,
    winnerCount: 1,
    prize: 'Free Steam Key',
    bonusEntries: [
        {
            // Members who have the "Nitro Boost" role get 2 bonus entries
            bonus: (member, giveaway) => (member.roles.cache.some((r) => r.name === 'Nitro Boost') ? 2 : null),
            cumulative: false
        }
    ]
});

Note (only for proficients): if you want to use values of global variables inside of the function without using giveaway.extraData, you can use the Function constructor.
Look at the exemptMembers section for more information on that.

Message Options

Options are available for the following messages:
GiveawayStartOptions#GiveawayMessages#winMessage, GiveawayRerollOptions#messages#congrat, GiveawayRerollOptions#messages#error and client.giveawaysManager.end(messageId, noWinnerMessage).

You can access giveaway properties in all embed or component properties that are a string.

The format, including all currently available options, looks like this:

message: {
    content: '',
    embed: new Discord.EmbedBuilder(),
    components: [new Discord.ActionRowBuilder()],
    replyToGiveaway: true
}

⚠ Note: When sending a component, content or embed is required.

Access giveaway properties in messages

You can access any giveaway property inside of giveaway messages with the format: {this.<property>}.
For example:

winMessage: 'Congratulations, {winners}! You won **{this.prize}**!\n{this.messageURL}'

Also, you can write JavaScript code inside of {}.
For example:

winMessage: 'Congratulations, {winners}! You won **{this.prize.toUpperCase()}**!\n{this.messageURL}'

If you want to fill in strings that are not messages of a giveaway, or just custom embeds, then you can use giveaway.fillInString(string) for strings, giveaway.fillInEmbed(embed) for embeds and giveaway.fillInComponents(embed) for components.

πŸ‡«πŸ‡· Translation

You can also pass a messages parameter for the start() function, if you want to translate the giveaway texts:

For example:

const duration = interaction.options.getString('duration');
const winnerCount = interaction.options.getInteger('winners');
const prize = interaction.options.getString('prize');

client.giveawaysManager.start(interaction.channel, {
    duration: ms(duration),
    winnerCount,
    prize,
    messages: {
        giveaway: 'πŸŽ‰πŸŽ‰ **GIVEAWAY** πŸŽ‰πŸŽ‰',
        giveawayEnded: 'πŸŽ‰πŸŽ‰ **GIVEAWAY ENDED** πŸŽ‰πŸŽ‰',
        title: '{this.prize}',
        drawing: 'Drawing: {timestamp}',
        dropMessage: 'Be the first to react with πŸŽ‰ !',
        inviteToParticipate: 'React with πŸŽ‰ to participate!',
        winMessage: 'Congratulations, {winners}! You won **{this.prize}**!\n{this.messageURL}',
        embedFooter: '{this.winnerCount} winner(s)',
        noWinner: 'Giveaway cancelled, no valid participations.',
        hostedBy: 'Hosted by: {this.hostedBy}',
        winners: 'Winner(s):',
        endedAt: 'Ended at'
    }
});

You can access giveaway properties in all these messages.

And for the reroll() function:

client.giveawaysManager.reroll(messageId, {
    messages: {
        congrat: ':tada: New winner(s): {winners}! Congratulations, you won **{this.prize}**!\n{this.messageURL}',
        error: 'No valid participations, no new winner(s) can be chosen!'
    }
});

You can access giveaway properties in these messages.
Message options are available in these messages.

Custom Database

You can use your custom database to save giveaways, instead of the json files (the "database" by default for discord-giveaways).
For this, you will need to extend the GiveawaysManager class, and replace some methods with your custom ones.
There are 4 methods you will need to replace:

⚠️ All the methods should be asynchronous to return a promise!

SQL examples

NoSQL examples