diff --git a/.eslintrc.js b/.eslintrc.js index 083116c..1d621db 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,5 +56,10 @@ module.exports = { 'eslint-comments/no-unlimited-disable': 'error', 'eslint-comments/no-unused-disable': 'error', 'eslint-comments/no-unused-enable': 'error', + + 'no-plusplus': 'off', + 'no-restricted-globals': "off", + 'radix': "off", + "consistent-return": "off", }, }; diff --git a/package.json b/package.json index 67c21a3..8dd2aac 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "dependencies": { "common-tags": "^1.8.0", "discord.js": "^11.5.1", - "got": "^9.6.0" + "got": "^9.6.0", + "moment": "^2.24.0", + "timestring": "^6.0.0" }, "devDependencies": { "eslint": "^5.16.0", diff --git a/src/Utils.js b/src/Utils.js index 73a9be9..d020bf0 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -35,6 +35,34 @@ class Utils { } return String(bool); } + + /** + * @description Takes a simple string and splits up the messages + * surrounded by the divider. (Strings not on divider will be ignored) + * @param {string} string - String to split. + * @param {string} divider - Where the string would be splitted. + * @returns {Array} Array of string splitted. + */ + static splitOnDivider(string, divider) { + let open = false; + let message = ''; + const array = []; + for (let i = 0; i < string.length; i++) { + const char = string.charAt(i); + if (char === divider) { + if (!open) { + open = true; + } else { + open = false; + array.push(message); + message = ''; + } + } else if (open) { + message += char; + } + } + return array; + } } module.exports = Utils; diff --git a/src/modules/Giveaway.js b/src/modules/Giveaway.js new file mode 100644 index 0000000..00d10ef --- /dev/null +++ b/src/modules/Giveaway.js @@ -0,0 +1,121 @@ +const { Collection, RichEmbed } = require('discord.js'); +const moment = require('moment'); + +const active = new Collection(); + +class Giveaway { + constructor({ time, description, price, winners, channelID, client }) { + this.client = client; + this.time = time; + this.price = price; + this.winners = isNaN(winners) ? 1 : parseInt(winners); + this.description = description; + this.messageID = null; + this.channelID = channelID; + this.interval = null; + this.paused = false; + this.users = []; + this.pausedTime = 0; + } + + run() { + return new Promise(resolve => { + const interval = () => { + const every = Math.sqrt(this.getTimeLeft() / 60000); + this.interval = setTimeout(async () => { + const message = await this.client.channels + .get(this.channelID) + .fetchMessage(this.messageID); + + if (this.getTimeLeft() < 1) { + clearTimeout(this.interval); + await message.edit(this.embed()); + return resolve(this); + } + await message.edit(this.embed()); + interval(); + }, Math.floor(every * 60000)); + }; + interval(); + }); + } + + embed() { + let duration = `Ends in **${moment + .duration(this.getTimeLeft(), 'ms') + .humanize()}**`; + if (this.getTimeLeft() < 1) { + const winners = this.drawWinners(); + winners.forEach(winner => { + this.client.users + .get(winner) + .send(`You wont the giveaway: **${this.price}** :tada: :tada:`); + }); + duration = `**Giveaway ended**\nWinner: <@${winners.join(', ')}>`; + } else if (this.paused) duration = '**Giveaway Paused**'; + return new RichEmbed() + .setTitle(this.price) + .setDescription( + ` + ${this.description ? `\n${this.description}\n` : ''} + ${duration} + Users participating: **${this.users.length}** + Total winners: **${this.winners}** + `, + ) + .setFooter('Click the reaction to enter!') + .setTimestamp(this.time) + .setColor(this.getTimeLeft() > 0 ? 'green' : 'red'); + } + + getTimeLeft() { + return this.time - Date.now(); + } + + async start() { + const channel = await this.client.channels.get(this.channelID); + + const message = await channel.send(this.embed()); + await message.react('🎉'); + this.messageID = message.id; + + active.set(this.messageID, this); + return this.run(); + } + + async pause() { + this.paused = true; + this.pausedTime = Date.now(); + clearTimeout(this.interval); + const channel = await this.client.channels.get(this.channelID); + return channel.send(this.embed()); + } + + resume() { + this.paused = false; + this.pausedTime = 0; + this.run(); + } + + enter(userID) { + this.users.push(userID); + } + + drawWinners() { + const winners = []; + for (let i = 0; i < this.winners; i++) { + const user = this.users[Math.floor(Math.random() * this.users.length)]; + if (!winners.includes(`<@${user}>`)) winners.push(`<@${user}>`); + else i--; + + if (this.users.length === i + 1) break; + } + + return winners; + } +} + +module.exports = { + Giveaway, + activeGiveaways: active, +}; diff --git a/src/modules/general/commands/giveaway.js b/src/modules/general/commands/giveaway.js new file mode 100644 index 0000000..efb139b --- /dev/null +++ b/src/modules/general/commands/giveaway.js @@ -0,0 +1,45 @@ +const moment = require('moment'); +const timestring = require('timestring'); + +const { Command } = require('../../../handler'); +const Utils = require('../../../Utils.js'); +const { Giveaway } = require('../../Giveaway'); + +module.exports = class extends Command { + constructor({ client }) { + super('giveaway', { + aliases: ['ga'], + info: + 'Wanna give things to people? Use this command to create giveaways.', + usage: 'giveaway "[time]" "[title]" "{description}" {winners}', + guildOnly: false, + }); + + this.client = client; + } + + run(message, args) { + const gArgs = Utils.splitOnDivider(args.join(' '), '"'); + + if (gArgs.length < 2) { + return message.reply('Wrong usage'); + } + + const time = timestring(gArgs[0], 'ms'); + const price = gArgs[1]; + const description = gArgs[2]; + + const giveaway = new Giveaway({ + time: moment() + .add(time) + .toDate() + .getTime(), + description, + price, + winners: args[args.length - 1], + channelID: message.channel.id, + client: this.client, + }); + return giveaway.start(); + } +}; diff --git a/src/modules/general/events/reactionAdd.js b/src/modules/general/events/reactionAdd.js new file mode 100644 index 0000000..7d6f2d6 --- /dev/null +++ b/src/modules/general/events/reactionAdd.js @@ -0,0 +1,21 @@ +const { Event } = require('../../../handler'); +const { activeGiveaways } = require('../../Giveaway'); + +module.exports = class extends Event { + constructor() { + super('messageReactionAdd'); + } + + run(client, reaction, user) { + if (user.bot) return; + + const message = reaction.message; + if (activeGiveaways.has(message.id)) { + const emoji = reaction.emoji; + if (emoji.name !== '🎉') return; + + const giveaway = activeGiveaways.get(message.id); + giveaway.enter(user.id); + } + } +};