├── source ├── cache.json ├── handlers │ ├── command.js │ ├── extenders.js │ └── giveaway.js ├── commands │ ├── invite.js │ ├── ping.js │ ├── help.js │ ├── commands.js │ ├── reload.js │ ├── draw.js │ ├── delete.js │ ├── eval.js │ ├── redraw.js │ └── create.js └── index.js ├── config.json.example ├── package.json ├── LICENSE ├── README.md └── .gitignore /source/cache.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "prefix":"-=", 3 | "token":"faSDasdASe.asGDfe-this.is-aVdf-not.a-EtaSt-ToKen-adfd", 4 | "owners":["156859037890117632"] 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "discord.js": "github:hydrabolt/discord.js#faf27fabc055cb3850e823cf36c0af21c0139c35", 4 | "json-maker": "^1.2.4", 5 | "klaw": "^2.0.0", 6 | "moment": "^2.18.1", 7 | "uws": "^9.14.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/handlers/command.js: -------------------------------------------------------------------------------- 1 | class Command { 2 | constructor(client, filePath, {name, aliases}) { 3 | this.client = client; 4 | 5 | this.path = filePath; 6 | 7 | this.name = name || "NULL"; 8 | 9 | this.aliases = aliases || new Array(); 10 | } 11 | } 12 | 13 | module.exports = Command; -------------------------------------------------------------------------------- /source/commands/invite.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "invite", 7 | }); 8 | } 9 | 10 | execute(message) { 11 | message.reply('Check your DMs. :wink:'); 12 | message.author.send(`You can invite me here: `); 13 | } 14 | }; -------------------------------------------------------------------------------- /source/commands/ping.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "ping", 7 | }); 8 | } 9 | 10 | execute(message) { 11 | message.channel.send(`Pinging...`).then(msg => { 12 | msg.edit(`Pong! | Client Ping: ${msg.createdTimestamp - message.createdTimestamp}ms | API Latency: ${Math.floor(this.client.pings[0])}`); 13 | }); 14 | } 15 | }; -------------------------------------------------------------------------------- /source/handlers/extenders.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, TextChannel, DMChannel } = require("discord.js"); 2 | 3 | MessageEmbed.prototype.send = function() { 4 | if (!this.sendToChannel || !(this.sendToChannel instanceof TextChannel || this.sendToChannel instanceof DMChannel)) return Promise.reject("Embed not created in a channel"); 5 | return this.sendToChannel.send("", { embed: this }); 6 | }; 7 | 8 | TextChannel.prototype.buildEmbed = DMChannel.prototype.buildEmbed = function() { 9 | return Object.defineProperty(new MessageEmbed(), "sendToChannel", { value: this }); 10 | }; 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 SirPacker, HyperCoder2975 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mr.-Giveaway 2 | An Open-Source Giveaway Bot for Discord 3 | ## Authors 4 | Created by Packer#9020 with help and guidance from HyperCoder#2975 5 | ## Config Setup 6 | ./config.json: 7 | ```json 8 | { 9 | "prefix":"PREFIX HERE", 10 | "token":"TOKEN HERE", 11 | "owners":["OWNER USER-ID HERE"] 12 | } 13 | ``` 14 | ## Notes 15 | 16 | ### Command Usage Explanation 17 | Basically, everything in `[]` is optional. Everything in `<>` is required. 18 | 19 | The most complex command you can have (for `mg!create` anyways) is `mg!create #giveaways -w 2 2m Test`, which creates a giveaway in #giveaways with 2 winners and will last 2 minutes. It will have the title "Test". 20 | 21 | The least complex is `mg!create 60 Test`, which creates a giveaway for 1 minute with 1 winner in the current channel. It will have the title of "Test". 22 | -------------------------------------------------------------------------------- /source/commands/help.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "help", 7 | }); 8 | } 9 | 10 | execute(message) { 11 | //message.author.send("```mg!create \nmg!delete ```"); 12 | message.channel.send("**Hi there!** I am known as Mr. Giveaway. While my origins are mostly unknown, people are known to associate me with Packer#9020. Ha. What fools. Tell me, are you looking for a automated giveaway? Well, are you? I have the perfect thing for you. Try `mg!invite` to get me into your server, then go through and start your own giveaway using the commands in `mg!commands`. I will be seeing you soon..."); 13 | } 14 | }; -------------------------------------------------------------------------------- /source/commands/commands.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "commands", 7 | aliases: ["cmds"] 8 | }); 9 | } 10 | 11 | execute(message) { 12 | const prefix = this.client.config.prefix; 13 | message.reply('Check your DMs. :wink:'); 14 | message.author.send(`\`\`\`md\nCommands for ${this.client.user.username}\n---------------------------\n` 15 | +`> ${prefix}create\n" + "Create a giveaway\n` 16 | +`> ${prefix}delete\n" + "Delete the previous giveaway\n` 17 | +`> ${prefix}draw\n" + "Force a draw\n` 18 | +`> ${prefix}redraw\n" + "Redraw a winner\n` 19 | +`> ${prefix}invite\n" + "Get an invite link for the bot\n` 20 | +`> ${prefix}server\n" + "Join the official Discord server\n` 21 | +`> ${prefix}ping\n" + "Check to see if the bot responds\`\`\``); 22 | } 23 | }; -------------------------------------------------------------------------------- /source/commands/reload.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "reload", 7 | }); 8 | } 9 | 10 | execute(message) { 11 | if (message.globalAdmin) { 12 | const match = /reload (.+)/i.exec(message.content); 13 | if (!match) return message.channel.send(`INVALID SYNTAX: \`${this.client.config.prefix}reload commmands:\``); 14 | 15 | this.client.reload(match[1]).then(() => { 16 | message.channel.buildEmbed() 17 | .setTitle('Command Successfully Reloaded') 18 | .setColor(0x00FF00) 19 | .setTimestamp() 20 | .send(); 21 | }).catch(err => { 22 | message.channel.buildEmbed() 23 | .setTitle('Command Failed to Reload') 24 | .setColor(0xFF0000) 25 | .setTimestamp() 26 | .send(); 27 | }); 28 | } else { return; } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | package-lock.json 61 | -------------------------------------------------------------------------------- /source/commands/draw.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "draw" 7 | }); 8 | } 9 | 10 | execute(message) { 11 | if (!message.guildAdmin && !message.globalAdmin) return message.channel.send('Invalid Permissions. `MANAGE_SERVER` permission or `MG Admin` role required.'); 12 | 13 | const match = /(?:draw)(?:\s+(?:<#)?(\d{17,20})(?:>)?)?(?:\s+(\d+))/i.exec(message.content); 14 | 15 | if (!match) return message.channel.send(`Invalid Command Usage: \`${this.client.config.prefix}draw [channel-mention|channel-id] \``); 16 | 17 | const channel = match[1] ? message.guild.channels.get(match[1]) || message.channel : message.channel; 18 | 19 | if (this.client.giveawayCache.filter(giveaway => giveaway.channel.id === channel.id).size <= 0) return message.channel.send(`There is no currently running giveaway in **${channel.name}**. Perhaps you meant \`mg!redraw\`?`); 20 | 21 | const giveaway = this.client.giveawayCache.get(`${channel.id}-${match[2]}`); 22 | if(!giveaway) return message.channel.send(`ERR: No giveaway is occuring in the specified channel with that ID.`); 23 | giveaway.endTime = Date.now() + 5 * 1000; 24 | giveaway.msg.embeds[0].timestamp = new Date(giveaway.endTime); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /source/commands/delete.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "delete", 7 | aliases: ["stop", "end"] 8 | }); 9 | } 10 | 11 | execute(message) { 12 | if(!message.guildAdmin && !message.globalAdmin) return message.channel.send('Invalid Permissions. `MANAGE_SERVER` permission or `MG Admin` role required.'); 13 | 14 | const match = /(?:delete|stop|end)(?:\s+(?:<#)?(\d{17,20})(?:>)?)?(?:\s+(\d+))/i.exec(message.content); 15 | 16 | if (!match) return message.channel.send(`Invalid Command Usage: \`${this.client.config.prefix}delete [channel-mention|channel-id] \``); 17 | 18 | const channel = match[1] ? message.guild.channels.get(match[1]) || message.channel : message.channel; 19 | 20 | if (this.client.giveawayCache.filter(giveaway => giveaway.channel.id === channel.id).size <= 0) return message.channel.send(`There is no currently running giveaway in **${channel.name}**.`); 21 | 22 | const a = this.client.giveawayCache.get(`${channel.id}-${match[2]}`); 23 | 24 | if(!a) return message.channel.send(`ERR: No giveaway is occuring in the specified channel with that ID.`); 25 | 26 | clearInterval(a.interval); 27 | 28 | a.msg.delete(); 29 | 30 | this.client.giveawayCache.delete(`${channel.id}-${match[2]}`); 31 | 32 | message.channel.send(`Giveaway **${match[2]}** has been deleted.`); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /source/commands/eval.js: -------------------------------------------------------------------------------- 1 | const Command = require('../handlers/command.js'); 2 | const MessageEmbed = require('discord.js').MessageEmbed; 3 | const util = require('util'); 4 | 5 | function embed(input, output, error = false) { 6 | return new MessageEmbed().setColor(error ? 0xFF0000 : 0x00FF00).addField("Input", input).addField(error ? "Error" : "Output", `\`\`\`${error ? "" : "js"}\n${output}\n\`\`\``).setFooter("Mr. Giveaway Eval"); 7 | } 8 | module.exports = class extends Command { 9 | constructor(client, filePath) { 10 | super(client, filePath, { 11 | name: "eval", 12 | }); 13 | } 14 | 15 | execute(message) { 16 | if (!message.globalAdmin) return; 17 | const code = message.content.slice(message.content.search(' ') + 1); 18 | if (!code.length) return message.channel.send('No code input.'); 19 | 20 | if (code.match(/token/gi)) return message.channel.send("The input requests the user token."); 21 | 22 | try { 23 | const after = eval(code); 24 | 25 | if (after instanceof Promise) { 26 | after.then(a => { 27 | message.channel.send("", { embed: embed(code, a instanceof Object ? util.inspect(a, { depth: 0 }) : a) }); 28 | }).catch(err => { 29 | message.channel.send("", { embed: embed(code, err, true) }); 30 | }); 31 | } else { 32 | message.channel.send("", { embed: embed(code, after instanceof Object ? util.inspect(after, { depth: 0 }) : after) }); 33 | } 34 | } catch(err) { 35 | message.channel.send("", { embed: embed(code, err, true) }); 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /source/commands/redraw.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | 3 | module.exports = class extends Command { 4 | constructor(client, filePath) { 5 | super(client, filePath, { 6 | name: "redraw", 7 | }); 8 | } 9 | 10 | async execute(message) { 11 | if (!message.guildAdmin && !message.globalAdmin) return message.channel.send('Invalid Permissions. `MANAGE_SERVER` permission or `MG Admin` role required.'); 12 | 13 | const match = /(?:redraw)(?:\s+(?:<#)?(\d{17,20})(?:>)?)?(?:\s+(\d+))/i.exec(message.content); 14 | 15 | if (!match) return message.channel.send('Invalid Command Usage: `mg!redraw [channel-mention|channel-id] `'); 16 | 17 | const channel = match[1] ? message.guild.channels.get(match[1]) || message.channel : message.channel; 18 | 19 | if (this.client.lastGiveawayCache.filter(giveaway => giveaway.channel.id === channel.id).size <= 0) return message.channel.send(`There has been no prior giveaway in ${channel.name}.`); 20 | 21 | const lastGiveaway = this.client.lastGiveawayCache.get(`${channel.id}-${match[2]}`); 22 | if(!lastGiveaway) return message.channel.send(`ERR: No giveaway occured in the specified channel with that ID.`); 23 | 24 | const winnerIds = lastGiveaway.winners.map(u => u.id); 25 | 26 | const users = (await lastGiveaway.msg.reactions.get("🎉").users.fetch()).array().filter(u => u.id !== lastGiveaway.msg.author.id && (lastGiveaway.winners.length && !winnerIds.includes(u.id))); 27 | 28 | if (!users.length) return message.channel.send("All users who entered were chosen."); 29 | 30 | const winner = lastGiveaway.draw(users); 31 | lastGiveaway.winners.push(winner); 32 | channel.send(`Congratulations, ${winner.toString()}! You won the redraw for the giveaway for **${lastGiveaway.title}**!`); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /source/commands/create.js: -------------------------------------------------------------------------------- 1 | const Command = require("../handlers/command.js"); 2 | const Giveaway = require("../handlers/giveaway.js"); 3 | 4 | module.exports = class extends Command { 5 | constructor(client, filePath) { 6 | super(client, filePath, { 7 | name: "create", 8 | aliases: ["start", "begin"] 9 | }); 10 | } 11 | 12 | async execute(message) { 13 | if (!message.channel.permissionsFor(message.guild.me).has(['EMBED_LINKS', 'ADD_REACTIONS'])) return message.channel.send('Invalid Permissions. For simplicity, `EMBED_LINKS` and `ADD_REACTIONS` permissions are required.'); 14 | if (!message.guildAdmin && !message.globalAdmin) return message.channel.send('Invalid Permissions. `MANAGE_SERVER` permission or `MG Admin` role required.'); 15 | 16 | const match = /(?:start|begin|create)(?:\s+?)?(?:\s+-w\s+(\d+))?\s+(?:(\d+)|(?:(\d+)\s*d(?:ays)?)?\s*(?:(\d+)\s*h(?:ours|rs|r)?)?\s*(?:(\d+)\s*m(?:inutes|in)?)?\s*(?:(\d+)\s*s(?:econds|ec)?)?)\s+(.+)/i.exec(message.content); 17 | if (!match) return message.channel.send(`Invalid Command Usage: \`${this.client.config.prefix}create [channel-mention|channel-id][\'-w \'] <#d#h#m#s|time-in-seconds> \``); 18 | 19 | const channel = message.guild.channels.get(match[1]) || message.channel; 20 | 21 | //if (this.client.giveawayCache.has(channel.id)) return message.channel.send(`There is already a giveaway in ${channel.name}.`); 22 | 23 | //const filtered = []; 24 | 25 | const filtered = this.client.giveawayCache.filter(giveaway => giveaway.channel.id === (match[1] ? match[1] : message.channel.id)).size; 26 | 27 | if (filtered >= 5) return message.channel.send(`ERR: You may only have a maximum of 5 giveaways per channel.`); 28 | 29 | /*Object.keys(this.client.giveawayCache).forEach(key => key.includes(match[1] ? message.guild.channels.get(match[1]) : message.channel.id) ? filtered.push(key) : null); 30 | 31 | if(filtered.length > 5) return message.channel.send(`ERR: You may only have a maximum of 5 giveaways per channel.`);*/ 32 | 33 | if (message.deletable) message.delete({ timeout: 1000, reason: "Giveaway Bot" }); 34 | 35 | new Giveaway(this.client, message, match); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /source/index.js: -------------------------------------------------------------------------------- 1 | const klaw = require('klaw'); 2 | const path = require('path'); 3 | require("./handlers/extenders.js"); 4 | const { Client, Collection, RichEmbed } = require('discord.js'); 5 | const moment = require("moment"); 6 | const cache = require("./cache.json"); 7 | const fs = require("fs"); 8 | const { join } = require("path"); 9 | 10 | const Giveaway = require("./handlers/giveaway.js"); 11 | 12 | const commandsPath = path.join(__dirname, "commands"); 13 | 14 | new class extends Client { 15 | constructor() { 16 | super(); 17 | 18 | this.config = require('../config.json'); 19 | 20 | this.commands = new Collection(); 21 | 22 | this.init(); 23 | 24 | this.giveawayCache = new Collection(); 25 | this.lastGiveawayCache = new Collection(); 26 | 27 | this.on('ready', () => { 28 | console.log(`Client connected with ${this.guilds.size} guilds, ${this.channels.size} channels, and ${this.users.size} users.`); 29 | 30 | this.user.setActivity(`${this.config.prefix}help | ${this.guilds.size} Guilds`); 31 | setInterval(() => this.user.setActivity(`${this.config.prefix}help | ${this.guilds.size} Guilds`), 1000 * 60 * 2); 32 | 33 | if (cache.length) cache.forEach(g => new Giveaway(this, null, g.match, g)); 34 | this.clearCache(); 35 | }); 36 | 37 | this.on('message', async message => { 38 | if (message.author.bot || !message.member) return; 39 | if (!message.content.startsWith(this.config.prefix)) return; 40 | 41 | message.globalAdmin = !!this.config.owners.includes(message.author.id); 42 | 43 | const content = message.content.slice(this.config.prefix.length); 44 | 45 | const command = await this.fetchCommand(content.split(' ')[0]); 46 | if (!command) return; 47 | 48 | message.guildAdmin = message.member.hasPermission("MANAGE_GUILD") || message.member.roles.map(r => r.name).includes("MG Admin"); 49 | 50 | command.execute(message); 51 | }); 52 | 53 | this.login(this.config.token); 54 | } 55 | 56 | async clearCache() { 57 | fs.writeFile(join(__dirname, "cache.json"), "[]", err => { 58 | if (err) throw err; 59 | return null; 60 | }); 61 | } 62 | 63 | fetchCommand(text) { 64 | return new Promise((resolve, reject) => { 65 | if (this.commands.has(text)) return resolve(this.commands.get(text)); 66 | this.commands.forEach(c => { if (c.aliases && c.aliases.includes(text)) return resolve(c); }); 67 | return resolve(); 68 | }); 69 | } 70 | 71 | init() { 72 | klaw(commandsPath).on("data", item => { 73 | const file = path.parse(item.path); 74 | if (!file.ext || file.ext !== ".js") return; 75 | 76 | const command = new (require(`${file.dir}/${file.base}`))(this, item.path); 77 | this.commands.set(command.name, command); 78 | }); 79 | } 80 | 81 | _reload(filePath) { 82 | return new Promise((resolve, reject) => { 83 | delete require.cache[filePath]; 84 | const command = new (require(filePath))(this, filePath); 85 | this.commands.set(command.name, command); 86 | return resolve(); 87 | }); 88 | } 89 | 90 | reload(entry, message) { 91 | return new Promise((resolve, reject) => { 92 | const args = /(\w+)(?::(\w+))?/i.exec(entry); 93 | if (!args && entry !== "all") return reject("Invalid Syntax"); 94 | 95 | const mod = args ? args[1] : null; 96 | const all = entry === "all"; 97 | 98 | if (all || mod === "events") { 99 | 100 | } else if (mod === "commands") { 101 | const command = args[2]; 102 | 103 | if (command) { 104 | if (!this.commands.has(command)) return reject(); 105 | 106 | this._reload(this.commands.get(command).path).then(() => { 107 | return resolve("Good!"); 108 | }); 109 | } else { 110 | this.commands.forEach(c => { 111 | this._reload(c.path); 112 | }); 113 | 114 | return resolve("Good!"); 115 | } 116 | } 117 | }); 118 | 119 | /* 120 | const match = /command(?::)?(.+)?/i.exec(entry); 121 | if (!match) return; 122 | if (match[1] === "all") { 123 | this.commands.clear(); 124 | this.init(); 125 | return; 126 | } 127 | if (this.commands.has(match[1])) { 128 | this.commands.delete(match[1]); 129 | 130 | const command = new (require(`${__dirname}/commands/${match[1]}.js`))(this); 131 | this.commands.set(command.name, command); 132 | 133 | message.channel.buildEmbed() 134 | .setTitle('Command Successfully Reloaded') 135 | .setDescription(`Command: \`${match[1]}\``) 136 | .setTimestamp() 137 | .send(); 138 | } else { 139 | message.channel.buildEmbed() 140 | .setTitle('Command Failed to Reload') 141 | .setDescription(`Command: \`${match[1]}\``) 142 | .setTimestamp() 143 | .send(); 144 | }*/ 145 | } 146 | }; 147 | 148 | process 149 | .on("uncaughtException", err => console.error(err.stack)) 150 | .on("unhandledRejection", err => console.error(err.stack)); 151 | -------------------------------------------------------------------------------- /source/handlers/giveaway.js: -------------------------------------------------------------------------------- 1 | class Giveaway { 2 | constructor(client, message, match, data) { 3 | const [full, channel, winnerCount, timeInSeconds, days, hours, minutes, seconds, title] = match; 4 | 5 | this.client = client; 6 | 7 | this.message = message; 8 | 9 | this.channel = data ? this.client.guilds.get(data.guild).channels.get(data.channel) : message.guild.channels.get(channel) || message.channel; 10 | 11 | this.winnerCount = winnerCount || 1; 12 | 13 | this.length = timeInSeconds ? timeInSeconds : (60 * 60 * 24 * (days ? Number(days) : 0)) + (60 * 60 * (hours ? Number(hours) : 0)) + (60 * (minutes ? Number(minutes) : 0)) + (seconds ? Number(seconds) : 0); 14 | 15 | this.remaining = data ? data.remaining : this.length; 16 | 17 | this.endTime = Date.now() + this.length * 1000; 18 | 19 | this.title = title; 20 | 21 | this.winners = []; 22 | 23 | this.msg; 24 | 25 | this.interval; 26 | 27 | this.suffix = this.client.giveawayCache.filter(giveaway => giveaway.channel.id === this.channel.id).size+1; 28 | 29 | this.match = match; 30 | 31 | this.valid = true; 32 | 33 | if (data) { this.continue(data); } else { this.init(); } 34 | } 35 | 36 | async init() { 37 | this.msg = await this.channel.buildEmbed() 38 | .setColor(0xcc8822) 39 | .setTitle(this.title) 40 | .setDescription(`Possible Winners: ${this.winnerCount}\nReact with :tada: to enter!`) 41 | .setFooter(`${this.suffix} | Ends at`) 42 | .setTimestamp(new Date(this.endTime)) 43 | .send(); 44 | 45 | this.msg.react("🎉"); 46 | 47 | this.interval = setInterval(() => this.intervalFunction(), 1000); 48 | 49 | this.client.giveawayCache.set(`${this.msg.channel.id}-${this.suffix}`, this); 50 | } 51 | 52 | async continue(data) { 53 | this.msg = await this.client.channels.get(data.channel).messages.fetch(data.msg).catch(() => { 54 | clearInterval(this.interval); 55 | this.client.lastGiveawayCache.set(`${this.msg.channel.id}-${this.suffix}`, this); 56 | this.client.giveawayCache.delete(`${this.msg.channel.id}-${this.suffix}`); 57 | }); 58 | this.winnerCount = await data.winnerCount; 59 | this.title = await data.title; 60 | this.interval = setInterval(() => this.intervalFunction(), 1000); 61 | 62 | this.client.giveawayCache.set(`${this.msg.channel.id}-${this.suffix}`); 63 | } 64 | 65 | async intervalFunction() { 66 | this.remaining--; 67 | 68 | this.channel.messages.fetch(this.msg.id).catch(() => { 69 | clearInterval(this.interval); 70 | this.client.lastGiveawayCache.set(`${this.msg.channel.id}-${this.suffix}`, this); 71 | this.client.giveawayCache.delete(`${this.msg.channel.id}-${this.suffix}`); 72 | }); 73 | 74 | if(Date.now() >= this.endTime){ 75 | clearInterval(this.interval); 76 | this.client.lastGiveawayCache.set(`${this.msg.channel.id}-${this.suffix}`, this); 77 | this.client.giveawayCache.delete(`${this.msg.channel.id}-${this.suffix}`); 78 | 79 | const embed = this.msg.embeds[0]; 80 | embed.color = 0x171c23; 81 | 82 | //const users = await this.msg.reactions.get("%F0%9F%8E%89").users.fetch(); 83 | const users = await this.msg.reactions.get("🎉").users.fetch(); 84 | const list = users.array().filter(u => u.id !== this.msg.author.id); 85 | 86 | if (!list.length) { 87 | embed.description = `Winner: No one.`; 88 | embed.footer.text = `Giveaway Finished`; 89 | 90 | return this.msg.edit({ embed }); 91 | } 92 | 93 | for (let i = 0; i < this.winnerCount; i++) { 94 | const x = this.draw(list); 95 | 96 | if (!this.winners.includes(x)) this.winners.push(x); 97 | } 98 | 99 | embed.description = `Winner: ${this.winners.filter(u => u !== undefined && u !== null).map(u => u.toString()).join(", ")}`; 100 | embed.footer.text = `${this.suffix} | Giveaway Finished`; 101 | 102 | this.msg.edit({ embed }); 103 | 104 | if (this.winners.length) this.msg.channel.send(`Congratulations, ${this.winners.map(u => u.toString()).join(", ")}! You won the giveaway for **${this.title}**!`); 105 | } 106 | 107 | /*if (this.remaining >= 5 && this.remaining % 5 === 0) { 108 | const embed = this.msg.embeds[0]; 109 | embed.footer.text = `Time Remaining: ${time}`; 110 | this.msg.edit({ embed }); 111 | } else if (this.remaining < 5 && this.remaining > 0) { 112 | const embed = this.msg.embeds[0]; 113 | embed.footer.text = `Time Remaining: ${time}`; 114 | this.msg.edit({ embed }); 115 | } else if (this.remaining === 0) { 116 | clearInterval(this.interval); 117 | this.client.lastGiveawayCache.set(this.msg.channel.id, this); 118 | this.client.giveawayCache.delete(this.msg.channel.id); 119 | 120 | const embed = this.msg.embeds[0]; 121 | embed.color = 0x171c23; 122 | 123 | //const users = await this.msg.reactions.get("%F0%9F%8E%89").users.fetch(); 124 | const users = await this.msg.reactions.get("🎉").fetchUsers(); 125 | const list = users.filterArray(u => u.id !== this.msg.author.id); 126 | 127 | if (!list.length) { 128 | embed.description = `Winner: No one.`; 129 | embed.footer.text = `Giveaway Finished`; 130 | 131 | return this.msg.edit({ embed }); 132 | } 133 | 134 | for (let i = 0; i < this.winnerCount; i++) { 135 | const x = this.draw(list); 136 | 137 | if (!this.winners.includes(x)) this.winners.push(x); 138 | } 139 | 140 | embed.description = `Winner: ${this.winners.filter(u => u !== undefined && u !== null).map(u => u.toString()).join(", ")}`; 141 | embed.footer.text = `Giveaway Finished`; 142 | 143 | this.msg.edit({ embed }); 144 | 145 | if (this.winners.length) this.msg.channel.send(`Congratulations, ${this.winners.map(u => u.toString()).join(", ")}! You won the giveaway for **${this.title}**!`); 146 | }*/ 147 | } 148 | 149 | shuffle(arr) { 150 | for (let i = arr.length; i; i--) { 151 | const j = Math.floor(Math.random() * i); 152 | [arr[i - 1], arr[j]] = [arr[j], arr[i - 1]]; 153 | } 154 | return arr; 155 | } 156 | 157 | draw(list) { 158 | const shuffled = this.shuffle(list); 159 | return shuffled[Math.floor(Math.random() * shuffled.length)]; 160 | } 161 | } 162 | 163 | module.exports = Giveaway; 164 | --------------------------------------------------------------------------------