diff --git a/README.md b/README.md index d43bf2fa..96d8feeb 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Discord Giveaways is a powerful [Node.js](https://nodejs.org) module that allows ## Installation -```js +```bash npm install --save discord-giveaways ``` @@ -59,7 +59,7 @@ const manager = new GiveawaysManager(client, { client.giveawaysManager = manager; client.on('ready', () => { - console.log('I\'m ready!'); + console.log('Bot is ready!'); }); client.login(process.env.DISCORD_BOT_TOKEN); @@ -75,7 +75,6 @@ You can pass an options object to customize the giveaways. Here is a list of the ```js client.on('interactionCreate', (interaction) => { - const ms = require('ms'); if (interaction.isCommand() && interaction.commandName === 'start') { @@ -86,13 +85,15 @@ client.on('interactionCreate', (interaction) => { const winnerCount = interaction.options.getInteger('winners'); const prize = interaction.options.getString('prize'); - client.giveawaysManager.start(interaction.channel, { - duration: ms(duration), - winnerCount, - prize - }).then((gData) => { - console.log(gData); // {...} (messageId, end date and more) - }); + 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! } }); @@ -110,37 +111,42 @@ This allows you to start a new giveaway. Once the `start()` function is called, #### ⚠ 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. + +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. ```js -const giveaway = -// Search with giveaway prize -client.giveawaysManager.giveaways.find((g) => g.guildId === interaction.guildId && g.prize === interaction.options.getString('query')) || -// Search with messageId -client.giveawaysManager.giveaways.find((g) => g.guildId === interaction.guildId && g.messageId === interaction.options.getString('query')); +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.channel.send('Unable to find a giveaway for `'+ args.join(' ') +'`.'); +if (!giveaway) return interaction.reply(`Unable to find a giveaway for \`${query}\`.`); ``` ### Reroll a giveaway ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'reroll') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.reroll(messageId).then(() => { - interaction.channel.send('Success! Giveaway rerolled!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` - **options.winnerCount**: the number of winners to pick. - **options.messages**: an object with the "congrat" and the "error" message. [Usage example](https://github.com/Androz2091/discord-giveaways#-translation). +- **options.messages.replyWhenNoWinner**: Whether or not to send the "error" message when there is no winner. @@ -150,18 +156,20 @@ client.on('interactionCreate', (interaction) => { ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'edit') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.edit(messageId, { - addTime: 5000, - newWinnerCount: 3, - newPrize: 'New Prize!' - }).then(() => { - interaction.channel.send('Success! Giveaway updated!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` @@ -171,7 +179,7 @@ client.on('interactionCreate', (interaction) => { - **options.addTime**: the number of milliseconds to add to the giveaway duration. - **options.setEndTimestamp**: the timestamp of the new end date (for example, for the giveaway to be ended in 1 hour, set it to `Date.now() + 60000`). - **options.newMessages**: the new giveaway messages. Will get merged with the existing object, if there. -^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). + ^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). - **options.newExtraData**: the new extra data value for the giveaway. - **options.newBonusEntries**: the new BonusEntry objects (for example, to change the amount of entries). - **options.newExemptMembers**: the new filter function to exempt members from winning the giveaway. @@ -183,14 +191,16 @@ client.on('interactionCreate', (interaction) => { ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'delete') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.delete(messageId).then(() => { - interaction.channel.send('Success! Giveaway deleted!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` @@ -203,32 +213,36 @@ client.on('interactionCreate', (interaction) => { ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'end') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.end(messageId).then(() => { - interaction.channel.send('Success! Giveaway ended!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` -- **noWinnerMessage**: Sent in the channel if there is no valid winner for the giveaway. [Message Options](https://github.com/Androz2091/discord-giveaways#message-options) +- **noWinnerMessage**: Sent in the channel if there is no valid winner for the giveaway. [Message Options](https://github.com/Androz2091/discord-giveaways#message-options) ### Pause a giveaway ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'pause') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.pause(messageId).then(() => { - interaction.channel.send('Success! Giveaway paused!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` @@ -237,7 +251,7 @@ client.on('interactionCreate', (interaction) => { - **options.unPauseAfter**: the number of milliseconds after which the giveaway will automatically unpause. - **options.embedColor**: the color of the embed when the giveaway is paused. - **options.infiniteDurationText**: The text that gets displayed next to `GiveawayMessages#drawing` in the paused embed, when there is no `unPauseAfter`. -^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). + ^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). ⚠️ **Note**: the pause function overwrites/edits the [pauseOptions object property](https://github.com/Androz2091/discord-giveaways#pause-options) of a giveaway! @@ -245,14 +259,16 @@ client.on('interactionCreate', (interaction) => { ```js client.on('interactionCreate', (interaction) => { - if (interaction.isCommand() && interaction.commandName === 'unpause') { const messageId = interaction.options.getString('message_id'); - client.giveawaysManager.unpause(messageId).then(() => { - interaction.channel.send('Success! Giveaway unpaused!'); - }).catch((err) => { - interaction.channel.send(`An error has occurred, please check and try again.\n\`${err}\``); - }); + 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}\``); + }); } }); ``` @@ -294,7 +310,7 @@ client.giveawaysManager.start(interaction.channel, { 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', `return !member.roles.cache.some((r) => r.name === \'${roleName}\')`), + exemptMembers: new Function('member', `return !member.roles.cache.some((r) => r.name === \'${roleName}\')`) }); ``` @@ -318,7 +334,7 @@ client.giveawaysManager.start(interaction.channel, { - **lastChance.enabled**: if the last chance system is enabled. - **lastChance.content**: the text of the embed when the last chance system is enabled. -^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). + ^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). - **lastChance.threshold**: the number of milliseconds before the giveaway ends when the last chance system will be enabled. - **lastChance.embedColor**: the color of the embed when last chance is enabled. @@ -345,11 +361,11 @@ client.giveawaysManager.start(interaction.channel, { - **pauseOptions.isPaused**: if the giveaway is paused. - **pauseOptions.content**: the text of the embed when the giveaway is paused. -^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). + ^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). - **pauseOptions.unPauseAfter**: the number of milliseconds, or a timestamp in milliseconds, after which the giveaway will automatically unpause. - **pauseOptions.embedColor**: the color of the embed when the giveaway is paused. - **pauseOptions.infiniteDurationText**: The text that gets displayed next to `GiveawayMessages#drawing` in the paused embed, when there is no `unPauseAfter`. -^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). + ^^^ You can [access giveaway properties](https://github.com/Androz2091/discord-giveaways#access-giveaway-properties-in-messages). @@ -362,10 +378,10 @@ client.giveawaysManager.start(interaction.channel, { duration: 60000, winnerCount: 1, prize: 'Free Steam Key', - bonusEntries: [ + bonusEntries: [ { // Members who have the "Nitro Boost" role get 2 bonus entries - bonus: (member) => member.roles.cache.some((r) => r.name === 'Nitro Boost') ? 2 : null, + bonus: (member) => (member.roles.cache.some((r) => r.name === 'Nitro Boost') ? 2 : null), cumulative: false } ] @@ -386,10 +402,13 @@ client.giveawaysManager.start(interaction.channel, { winnerCount: 1, prize: 'Free Steam Key', bonusEntries: [ - { + { // Members who have the role which is assigned to "roleName" get the amount of bonus entries which is assigned to "roleBonusEntries" - bonus: new Function('member', `return member.roles.cache.some((r) => r.name === \'${roleName}\') ? ${roleBonusEntries} : null`), - cumulative: false + bonus: new Function( + 'member', + `return member.roles.cache.some((r) => r.name === \'${roleName}\') ? ${roleBonusEntries} : null` + ), + cumulative: false } ] }); @@ -422,13 +441,15 @@ message: { You can access any giveaway property inside of giveaway messages with the format: `{this.}`. For example: + ```js winMessage: 'Congratulations, {winners}! You won **{this.prize}**!\n{this.messageURL}' ``` -Also, you can write JavaScript code inside of the `{}`. +Also, you can write JavaScript code inside of `{}`. For example: + ```js winMessage: 'Congratulations, {winners}! You won **{this.prize.toUpperCase()}**!\n{this.messageURL}' ``` @@ -437,17 +458,17 @@ If you want to fill in strings that are not messages of a giveaway, or just cust ## 🇫🇷 Translation -You can also pass a `messages` parameter for `start()` function, if you want to translate the bot text: +You can also pass a `messages` parameter for the `start()` function, if you want to translate the giveaway texts: - **options.messages.giveaway**: the message that will be displayed above the embeds. - **options.messages.giveawayEnded**: the message that will be displayed above the embeds when the giveaway is ended. - **options.messages.drawing**: the message that displays the drawing timestamp. - **options.messages.dropMessage**: the message that will be displayed for drop giveaways. - **options.messages.inviteToParticipate**: the message that invites users to participate. -- **options.messages.winMessage**: the message that will be displayed to congratulate the winner(s) when the giveaway is ended. -^^^ [Message options](https://github.com/Androz2091/discord-giveaways#message-options) are available in this message. +- **options.messages.winMessage**: the message that will be displayed to congratulate the winner(s) when the giveaway is ended. + ^^^ [Message options](https://github.com/Androz2091/discord-giveaways#message-options) are available in this message. - **options.messages.embedFooter**: the message displayed at the bottom of the main (not ended) embed. -^^^ An empty string can be used for "deactivation", or [`iconURL` can be set](https://discord-giveaways.js.org/global.html#EmbedFooterObject). + ^^^ An empty string can be used for "deactivation", or [`iconURL` can be set](https://discord-giveaways.js.org/global.html#EmbedFooterObject). - **options.messages.noWinner**: the message that is displayed if no winner can be drawn. - **options.messages.hostedBy**: the message to display the host of the giveaway. - **options.messages.winners**: simply the expression "Winner(s):" in your language. @@ -475,7 +496,7 @@ client.giveawaysManager.start(interaction.channel, { noWinner: 'Giveaway cancelled, no valid participations.', hostedBy: 'Hosted by: {this.hostedBy}', winners: 'Winner(s):', - endedAt: 'Ended at', + endedAt: 'Ended at' } }); ``` @@ -486,11 +507,11 @@ And for the `reroll()` function: ```js 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!' - } - }); + 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!' + } +}); ``` - **options.messages.congrat**: the congratulatory message. @@ -501,7 +522,9 @@ You can [access giveaway properties](https://github.com/Androz2091/discord-givea ## 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: +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: - `getAllGiveaways`: this method returns an array of stored giveaways. - `saveGiveaway`: this method stores a new giveaway in the database. @@ -511,16 +534,18 @@ You can use your custom database to save giveaways, instead of the json files (t **⚠️ All the methods should be asynchronous to return a promise!** **SQL examples** -- [MySQL](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/mysql.js) -- SQLite - - [Quick.db](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quick.db.js) - - [Enmap](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/enmap.js) + +- [MySQL](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/mysql.js) +- SQLite + - [Quick.db](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quick.db.js) + - [Enmap](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/enmap.js) **NoSQL examples** -- MongoDB - - [Mongoose](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/mongoose.js) - - [QuickMongo](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quickmongo.js) ⚠️ Not recommended for high giveaway usage, use the `mongoose` example instead -- [Apache CouchDB - Nano](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/nano.js) -- Replit Database ⚠️ Only usable if your bot is hosted on [Replit](https://replit.com/) - - [@replit/database](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/replit.js) - - [Quick.Replit](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quick.replit.js) \ No newline at end of file + +- MongoDB + - [Mongoose](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/mongoose.js) + - [QuickMongo](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quickmongo.js) ⚠️ Not recommended for high giveaway usage, use the `mongoose` example instead +- [Apache CouchDB - Nano](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/nano.js) +- Replit Database ⚠️ Only usable if your bot is hosted on [Replit](https://replit.com/) + - [@replit/database](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/replit.js) + - [Quick.Replit](https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/quick.replit.js) diff --git a/src/Constants.js b/src/Constants.js index 13f6eb41..dd531f5c 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -164,12 +164,14 @@ exports.GiveawaysManagerOptions = { * @property {Object} [messages] The messages used in this method. * @property {string|MessageObject} [messages.congrat=':tada: New winner(s): {winners}! Congratulations, you won **{this.prize}**!\n{this.messageURL}'] The message used if there are new winners. * @property {string|MessageObject} [messages.error='No valid participations, no new winner(s) can be chosen!'] The message used if no new winner(s) could be chosen. + * @property {boolean} [messages.replyWhenNoWinner=true] Whether or not to send the "error" message when there is no winner. */ exports.GiveawayRerollOptions = { winnerCount: null, 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!' + error: 'No valid participations, no new winner(s) can be chosen!', + replyWhenNoWinner: true } }; diff --git a/src/Giveaway.js b/src/Giveaway.js index bd650d0c..a8e03bb3 100644 --- a/src/Giveaway.js +++ b/src/Giveaway.js @@ -962,18 +962,22 @@ class Giveaway extends EventEmitter { } resolve(winners); } else { - const embed = this.fillInEmbed(options.messages.error.embed); - channel.send({ - content: this.fillInString(options.messages.error.content || options.messages.error), - embeds: embed ? [embed] : null, - components: this.fillInComponents(options.messages.error.components), - allowedMentions: this.allowedMentions, - reply: { - messageReference: - typeof options.messages.error.replyToGiveaway === 'boolean' ? this.messageId : undefined, - failIfNotExists: false - } - }); + if (options.messages.replyWhenNoWinner !== false) { + const embed = this.fillInEmbed(options.messages.error.embed); + channel.send({ + content: this.fillInString(options.messages.error.content || options.messages.error), + embeds: embed ? [embed] : null, + components: this.fillInComponents(options.messages.error.components), + allowedMentions: this.allowedMentions, + reply: { + messageReference: + typeof options.messages.error.replyToGiveaway === 'boolean' + ? this.messageId + : undefined, + failIfNotExists: false + } + }); + } resolve([]); } }); diff --git a/typings/index.d.ts b/typings/index.d.ts index ba58849b..e85d5309 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -214,6 +214,7 @@ export interface GiveawayRerollOptions { messages?: { congrat?: string | MessageObject; error?: string | MessageObject; + replyWhenNoWinner?: boolean; }; } export interface GiveawayData {