├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── commands ├── addMember.js ├── help.js ├── listMembers.js ├── removeMember.js ├── replyPrompt.js ├── reset.js ├── showPrompt.js └── viewReply.js ├── docs └── screenshots │ ├── standup-bot-1.png │ ├── standup-bot-2.png │ └── standup-bot-3.png ├── index.js ├── models └── standup.model.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: node index.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Standup Bot 🤖 2 | > Discord bot for Scrum daily standups 3 | 4 | ## How-To: 5 | 6 | #### *Disclaimer:* 7 | 8 | This bot is not meant to replace your daily standups outright. It is more of a helper for your meetings. Instead of spending time going around asking the same questions everyday, time can be reserved for more important non-repetitive discussion and help. 9 | 10 | ### Initial Setup 11 | 12 | [Click Here](https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=355408) to add the bot to your server. You'll need to fill in `YOUR_CLIENT_ID` with your bots client id. 13 | > Note that the above requires you to have the **Manage Server** permission in this server 14 | 15 | The bot will then create an entry in the `MongoDB` database for the server, create the text channel `#daily-standups` and send an introduction. 16 | 17 | ### Commands 18 | > The prefix for this bot is `!` 19 | 20 | | Name | Description | Usage | Server or DM | | 21 | | ------- | -------------------------------------------------- | --------------------------------------- | ------------ | ------------------------------------------- | 22 | | `help` | *Shows all commands* | `!help [optional command name]` | **_both_** | | 23 | | `list` | *List of all members participating in the standup* | `!list` | Server | | 24 | | `am` | *Adds a new member to the standup* | `!am @ @ ...` | Server | | 25 | | `rm` | *Removes a member from the standup* | `!rm @ @ ...` | Server | | 26 | | `reset` | *Resets the standup* | `!reset` | Server | Use with caution, resets everything | 27 | | `show` | *Shows standup prompt* | `!show` | **_both_** | | 28 | | `reply` | *Reply to standup prompt* | `!reply @ [message]` | DM | `optional_server_id`: for multiple standups | 29 | | `view` | *View your standup response* | `!view @` | DM | `optional_server_id`: for multiple standups | 30 | 31 | 32 | ### Usage 33 | > Standup time is set to `10:30:00 PM EST` every weekday 34 | 35 | Anytime before the standup time, added members must DM the bot with the `reply` followed by their message. The bot will then upload this response to the database. 36 | Come standup time, the bot will create an Embed with all collected member responses *and* will include a `Hooligans` section with mentions of members who did not participate. 37 | This message will be posted to `#daily-standups`. 38 | 39 | After the message has been posted, the bot will delete all member responses, thus members will have to DM for the next standup. 40 | 41 | #### Made with 42 | 43 | - `Discord.js` and `node-schedule` for cron jobs 44 | - `MongoDB` with `mongoose` 45 | - `Heroku` for hosting 46 | - :heart: 47 | 48 | 49 | ### Screenshots 📸 50 |
51 | 52 |
53 | 54 |
55 |
56 | 57 | *Text channel creation and initial message on join (1920x1080)* 58 | 59 |
60 |
61 | 62 |
63 | 64 |
65 |
66 | 67 | *Private messaging bot with commands (1920x1080)* 68 | 69 |
70 |
71 | 72 |
73 | 74 |
75 |
76 | 77 | *Daily standup message example with hooligans (1920x1080)* 78 | 79 |
80 | -------------------------------------------------------------------------------- /commands/addMember.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | /** 3 | * !am - Adds a new member to the standup 4 | * NOTE: server admin can only preform this operation 5 | */ 6 | module.exports = { 7 | name: "am", 8 | usage: "@ @ ...", 9 | guildOnly: true, 10 | description: "Adds a new member to the standup", 11 | async execute(message, args) { 12 | if (!args.length) 13 | return message.channel.send( 14 | "Ruh Roh! You need to mention **_at least_** one member as argument!" 15 | ); 16 | 17 | standupModel 18 | .findById(message.guild.id) 19 | .then((standup) => { 20 | args.forEach((mention) => { 21 | if (mention.startsWith("<@") && mention.endsWith(">")) { 22 | mention = mention.slice(2, -1); 23 | 24 | if (mention.startsWith("!")) mention = mention.slice(1); 25 | 26 | const member = message.guild.members.cache.get(mention); 27 | 28 | if (member && standup.members.indexOf(member.id) == -1) 29 | standup.members.push(member.id); 30 | } 31 | }); 32 | 33 | standup 34 | .save() 35 | .then(() => message.channel.send("Members updated :tada:")) 36 | .catch((err) => { 37 | console.err(err); 38 | message.channel.send( 39 | "Oh no :scream:! An error occured somewhere in the matrix!" 40 | ); 41 | }); 42 | }) 43 | .catch((err) => { 44 | console.error(err); 45 | message.channel.send( 46 | "Oh no :scream:! An error occured somewhere in the matrix!" 47 | ); 48 | }); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /commands/help.js: -------------------------------------------------------------------------------- 1 | const PREFIX = "!"; 2 | 3 | /** 4 | * !help command - Lists out all the available commands 5 | */ 6 | module.exports = { 7 | name: "help", 8 | description: "Shows all commands", 9 | usage: "[command name]", 10 | execute(message, args) { 11 | const data = []; 12 | const { commands } = message.client; 13 | 14 | /** 15 | * If the user wants all the commands 16 | */ 17 | if (!args.length) { 18 | data.push("Here's a list of all my commands:"); 19 | let cmds = ""; 20 | commands.forEach(command => { 21 | cmds += (`\`${PREFIX}${command.name}\``).padEnd(6, '\t'); 22 | if(command.description) cmds += `\t*${command.description}*\n` 23 | }); 24 | data.push(cmds); 25 | data.push( 26 | `Try \`${PREFIX}help [command name]\` to get info on a specific command!` 27 | ); 28 | 29 | return message.channel.send(data, { split: true }).catch((error) => { 30 | console.error(error); 31 | message.reply( 32 | "Houston, we have a problem!" 33 | ); 34 | }); 35 | } 36 | 37 | /** 38 | * If the user specifies a command 39 | */ 40 | const name = args[0].toLowerCase(); 41 | const command = 42 | commands.get(name) || 43 | commands.find((c) => c.aliases && c.aliases.includes(name)); 44 | 45 | if (!command) { 46 | return message.reply("Uh Oh! Not a valid command"); 47 | } 48 | 49 | data.push(`**Name:** ${command.name}`); 50 | 51 | if (command.description) 52 | data.push(`**Description:** *${command.description}*`); 53 | if (command.usage) 54 | data.push(`**Usage:** \`${PREFIX}${command.name} ${command.usage}\``); 55 | 56 | data.push(`**Cooldown:** ${command.cooldown || 3} second(s)`); 57 | 58 | message.channel.send(data, { split: true }); 59 | 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /commands/listMembers.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | 3 | /** 4 | * !list - list all participating members 5 | */ 6 | module.exports = { 7 | name: "list", 8 | guildOnly: true, 9 | description: "List of all members participating in the standup", 10 | execute(message, args) { 11 | standupModel.findById(message.guild.id).then(standup => { 12 | let res = "Here are all members participating in the standup:\n"; 13 | if(!standup.members.length) { 14 | message.reply("there does not seem to be any members in the standup. Try `!am @ @ ...` to add member(s)") 15 | } else { 16 | standup.members.forEach(member => { 17 | res += `<@${member}>\t`; 18 | }); 19 | message.channel.send(res); 20 | } 21 | }).catch(err => { 22 | console.error(err); 23 | message.channel.send( 24 | "Oh no :scream:! An error occured somewhere in the matrix!" 25 | ); 26 | }) 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /commands/removeMember.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | /** 3 | * !rm - removes member from standup 4 | * NOTE: server admin can only preform this operation 5 | */ 6 | module.exports = { 7 | name: "rm", 8 | usage: "@ @ ...", 9 | guildOnly: true, 10 | description: "Removes a member from the standup", 11 | async execute(message, args) { 12 | if (!args.length) 13 | return message.channel.send( 14 | "Ruh Roh! You need to mention **_at least_** one member as argument!" 15 | ); 16 | 17 | standupModel 18 | .findById(message.guild.id) 19 | .then((standup) => { 20 | args.forEach((mention) => { 21 | if (mention.startsWith("<@") && mention.endsWith(">")) { 22 | mention = mention.slice(2, -1); 23 | 24 | if (mention.startsWith("!")) mention = mention.slice(1); 25 | 26 | const member = message.guild.members.cache.get(mention); 27 | 28 | if (member && standup.members.indexOf(member.id) != -1) 29 | standup.members = standup.members.filter( 30 | (id) => id !== member.id 31 | ); 32 | } 33 | }); 34 | 35 | standup 36 | .save() 37 | .then(() => message.channel.send("Members updated :tada:")) 38 | .catch((err) => { 39 | console.err(err); 40 | message.channel.send( 41 | "Oh no :scream:! An error occured somewhere in the matrix!" 42 | ); 43 | }); 44 | }) 45 | .catch((err) => { 46 | console.error(err); 47 | message.channel.send( 48 | "Oh no :scream:! An error occured somewhere in the matrix!" 49 | ); 50 | }); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /commands/replyPrompt.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | 3 | module.exports = { 4 | name: "reply", 5 | usage: "@ [your-message-here]", 6 | description: "Reply to standup prompt", 7 | execute(message, args) { 8 | if (message.channel.type === "dm") { 9 | if (!args.length || (args.length == 1 && args[0].startsWith("@"))) 10 | return message.reply( 11 | "Ruh Roh! You must provide a response as a message. No one likes a :ghost: as a team member :exclamation: :anger:" 12 | ); 13 | 14 | if (args[0].startsWith("@")) { 15 | standupModel 16 | .findById(args[0].slice(1)) 17 | .then((standup) => { 18 | if (standup.members.indexOf(message.author.id) !== -1) { 19 | standup.responses.set( 20 | message.author.id, 21 | args.splice(1).join(" ") 22 | ); 23 | 24 | standup 25 | .save() 26 | .then(() => message.channel.send("Updated Response :tada:")) 27 | .catch((err) => { 28 | console.error(err); 29 | message.channel.send( 30 | "Oh no :scream:! An error occured somewhere in the matrix!" 31 | ); 32 | }); 33 | } else { 34 | message.channel.send( 35 | "Ruh Roh! You must be a team member in this server standup to reply to the response!" 36 | ); 37 | } 38 | }) 39 | .catch((err) => { 40 | console.error(err); 41 | message.channel.send( 42 | "Oh no :scream:! An error occured somewhere in the matrix!" 43 | ); 44 | }); 45 | } else { 46 | standupModel 47 | .find() 48 | .then((standups) => { 49 | const userStandupList = standups.filter( 50 | (standup) => standup.members.indexOf(message.author.id) !== -1 51 | ); 52 | 53 | if (!userStandupList.length) { 54 | message.channel.send( 55 | "Ruh Roh! You must be a team member in ***__any__*** server standup to reply to the response!" 56 | ); 57 | } else if (userStandupList.length > 1) { 58 | message.channel.send( 59 | "Ruh Roh! Looks like you're a member in multiple standup servers!\nTry `!reply @ [your-message-here]` if you would like to reply to a *specific* standup server.\n**_Crunchy Hint:_** To get the serverId for *any* server, right-click the server icon and press `Copy ID`.\nNote that you may need Developer options turned on. But like, what kinda developer uses a standup bot **_AND DOESN'T TURN ON DEVELOPPER SETTINGS_** :man_facepalming:" 60 | ); 61 | } else { 62 | let [standup] = userStandupList; 63 | standup.responses.set( 64 | message.author.id, 65 | args.join(" ") 66 | ); 67 | standup 68 | .save() 69 | .then(() => message.channel.send("Updated Response :tada:")) 70 | .catch((err) => { 71 | console.error(err); 72 | message.channel.send( 73 | "Oh no :scream:! An error occured somewhere in the matrix!" 74 | ); 75 | }); 76 | } 77 | }) 78 | .catch((err) => { 79 | console.error(err); 80 | message.channel.send( 81 | "Oh no :scream:! An error occured somewhere in the matrix!" 82 | ); 83 | }); 84 | } 85 | } else { 86 | return message.reply("private DM me with `!reply` :bomb:"); 87 | } 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /commands/reset.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | 3 | /** 4 | * !reset - resets the standup (wipes from database, and re-inits) 5 | * NOTE: - server admin can only preform this operation 6 | * - command only works if text channel has been created already 7 | */ 8 | module.exports = { 9 | name: "reset", 10 | guildOnly: true, 11 | description: "Resets the standup", 12 | async execute(message, args) { 13 | // Check if user has perms 14 | if (!message.member.hasPermission("MANAGE_GUILD")) { 15 | return message.reply("You do not have the required permission!"); 16 | } 17 | 18 | let check = true; 19 | standupModel.findById(message.guild.id).then(standup => { 20 | standup.members.forEach(id => {if(standup.responses.has(id)) {standup.responses.delete(id);}}); 21 | standup.members = []; 22 | standup.save().then(() => message.channel.send("\nStandup successfully reset! :tada:\n*There are no memebers in the standup, and all responses have been cleared!*")).catch(err => { 23 | console.error(err); 24 | message.channel.send("Oh no :scream:! An error occured somewhere in the matrix!"); 25 | }) 26 | }).catch(err => { 27 | console.error(err); 28 | message.channel.send("Oh no :scream:! An error occured somewhere in the matrix!"); 29 | }) 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /commands/showPrompt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "show", 3 | description: "Shows standup prompt", 4 | async execute(message, args) { 5 | message.channel.send(` 6 | Here is the daily standup prompt: 7 | \`\`\` 8 | 1. What have you done since yesterday? 9 | 2. What are you planning on doing today? 10 | 3. Any impediments or stumbling blocks? 11 | \`\`\`Please make sure you have thought about your response **_very carefully_** as standups are more for *the entire team*. 12 | Once you are ready to respond, simply DM me with \`!reply ...\` where \`...\` represents your response. :stuck_out_tongue: 13 | `); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /commands/viewReply.js: -------------------------------------------------------------------------------- 1 | const standupModel = require("../models/standup.model"); 2 | 3 | module.exports = { 4 | name: "view", 5 | usage: "@", 6 | description: "View your standup response", 7 | execute(message, args) { 8 | if (message.channel.type === "dm") { 9 | if (args.length == 1 && !args[0].startsWith("@")) { 10 | return message.reply( 11 | "Ruh Roh! Thats an invalid command call, try `!help view` for more information." 12 | ); 13 | } else if (args.length && args[0].startsWith("@")) { 14 | standupModel 15 | .findById(args[0].slice(1)) 16 | .then((standup) => { 17 | if (standup.members.indexOf(message.author.id) !== -1) { 18 | if (standup.responses.has(message.author.id)) { 19 | message.reply( 20 | "Here is your response:\n" + 21 | standup.responses.get(message.author.id) 22 | ); 23 | } else { 24 | message.reply( 25 | "Ruh Roh! Looks like you do not have a response yet! Add one using the command `!reply @ [your-message-here]`." 26 | ); 27 | } 28 | } else { 29 | message.channel.send( 30 | "Ruh Roh! You must be a team member in this server standup!" 31 | ); 32 | } 33 | }) 34 | .catch((err) => { 35 | console.error(err); 36 | message.channel.send( 37 | "Oh no :scream:! An error occured somewhere in the matrix!" 38 | ); 39 | }); 40 | } else { 41 | standupModel 42 | .find() 43 | .then((standups) => { 44 | const userStandupList = standups.filter( 45 | (standup) => standup.members.indexOf(message.author.id) !== -1 46 | ); 47 | 48 | if (!userStandupList.length) { 49 | message.channel.send( 50 | "Ruh Roh! You must be a team member in ***__at least one__*** server standup to view your response!" 51 | ); 52 | } else if (userStandupList.length > 1) { 53 | message.channel.send( 54 | "Ruh Roh! Looks like you're a member in multiple standup servers!\nTry `!view @` if you would like to view a response for a *specific* standup server.\n**_Crunchy Hint:_** To get the serverId for *any* server, right-click the server icon and press `Copy ID`.\nNote that you may need Developer options turned on. But like, what kinda developer uses a standup bot **_AND DOESN'T TURN ON DEVELOPPER SETTINGS_** :man_facepalming:" 55 | ); 56 | } else { 57 | let [standup] = userStandupList; 58 | if (standup.responses.has(message.author.id)) { 59 | message.reply( 60 | "Here is your response:\n" + 61 | standup.responses.get(message.author.id) 62 | ); 63 | } else { 64 | message.reply( 65 | "Ruh Roh! Looks like you do not have a response yet! Add one using the command `!reply @ [your-message-here]`." 66 | ); 67 | } 68 | } 69 | }) 70 | .catch((err) => { 71 | console.error(err); 72 | message.channel.send( 73 | "Oh no :scream:! An error occured somewhere in the matrix!" 74 | ); 75 | }); 76 | } 77 | } else { 78 | return message.reply("private DM me with `!view` :bomb:"); 79 | } 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /docs/screenshots/standup-bot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navn-r/standup-bot/dbd213043c3a3b13900aa22bc14778af664379a1/docs/screenshots/standup-bot-1.png -------------------------------------------------------------------------------- /docs/screenshots/standup-bot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navn-r/standup-bot/dbd213043c3a3b13900aa22bc14778af664379a1/docs/screenshots/standup-bot-2.png -------------------------------------------------------------------------------- /docs/screenshots/standup-bot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navn-r/standup-bot/dbd213043c3a3b13900aa22bc14778af664379a1/docs/screenshots/standup-bot-3.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; // since I hate not using semicolons 2 | 3 | /** 4 | * Required Imports 5 | * - dotenv: .env support 6 | * - fs: file system support (for reading ./commands) 7 | * - mongoose: mongoDB client 8 | * - discord.js: discord (duh) 9 | * - schedule: for running the cron jobs 10 | * - standup.model: the model for the standup stored in mongo 11 | */ 12 | require("dotenv").config(); 13 | const fs = require("fs"); 14 | const mongoose = require("mongoose"); 15 | const { Client, MessageEmbed, Collection } = require("discord.js"); 16 | const schedule = require("node-schedule"); 17 | const standupModel = require("./models/standup.model"); 18 | 19 | const PREFIX = "!"; 20 | 21 | const standupIntroMessage = new MessageEmbed() 22 | .setColor("#ff9900") 23 | .setTitle("Daily Standup") 24 | .setURL("https://www.youtube.com/watch?v=dQw4w9WgXcQ") 25 | .setDescription( 26 | "This is the newly generated text channel used for daily standups! :tada:" 27 | ) 28 | .addFields( 29 | { 30 | name: "Introduction", 31 | value: `Hi! I'm Stan D. Upbot and I will be facilitating your daily standups from now on.\nTo view all available commands, try \`${PREFIX}help\`.`, 32 | }, 33 | { 34 | name: "How does this work?", 35 | value: `Anytime before the standup time \`10:30 AM EST\`, members would private DM me with the command \`${PREFIX}show\`, I will present the standup prompt and they will type their response using the command \`${PREFIX}reply @ [your-message-here]\`. I will then save their response in my *secret special chamber of data*, and during the designated standup time, I would present everyone's answer to \`#daily-standups\`.`, 36 | }, 37 | { 38 | name: "Getting started", 39 | value: `*Currently*, there are no members in the standup! To add a member try \`${PREFIX}am \`.`, 40 | } 41 | ) 42 | .setFooter( 43 | "https://github.com/navn-r/standup-bot", 44 | "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" 45 | ) 46 | .setTimestamp(); 47 | 48 | const dailyStandupSummary = new MessageEmbed() 49 | .setColor("#ff9900") 50 | .setTitle("Daily Standup") 51 | .setURL("https://www.youtube.com/watch?v=dQw4w9WgXcQ") 52 | .setFooter( 53 | "https://github.com/navn-r/standup-bot", 54 | "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" 55 | ) 56 | .setTimestamp(); 57 | 58 | // lists .js files in commands dir 59 | const commandFiles = fs 60 | .readdirSync("./commands") 61 | .filter((file) => file.endsWith(".js")); 62 | 63 | // init bot client with a collection of commands 64 | const bot = new Client(); 65 | bot.commands = new Collection(); 66 | 67 | // Imports the command file + adds the command to the bot commands collection 68 | for (const file of commandFiles) { 69 | const command = require(`./commands/${file}`); 70 | bot.commands.set(command.name, command); 71 | } 72 | 73 | // mongodb setup with mongoose 74 | mongoose 75 | .connect(process.env.MONGO_URI, { 76 | useNewUrlParser: true, 77 | useCreateIndex: true, 78 | useUnifiedTopology: true, 79 | }) 80 | .catch((e) => console.error(e)); 81 | 82 | mongoose.connection.once("open", () => console.log("mongoDB connected")); 83 | 84 | bot.once("ready", () => console.log("Discord Bot Ready")); 85 | 86 | // when a user enters a command 87 | bot.on("message", async (message) => { 88 | if (!message.content.startsWith(PREFIX) || message.author.bot) return; 89 | 90 | const args = message.content.slice(PREFIX.length).trim().split(/ +/); 91 | const commandName = args.shift().toLowerCase(); 92 | 93 | if (!bot.commands.has(commandName)) return; 94 | 95 | if (message.mentions.users.has(bot.user.id)) 96 | return message.channel.send(":robot:"); 97 | 98 | const command = bot.commands.get(commandName); 99 | 100 | if (command.guildOnly && message.channel.type === "dm") { 101 | return message.channel.send("Hmm, that command cannot be used in a dm!"); 102 | } 103 | 104 | try { 105 | await command.execute(message, args); 106 | } catch (error) { 107 | console.error(error); 108 | message.channel.send(`Error 8008135: Something went wrong!`); 109 | } 110 | }); 111 | 112 | bot.on("guildCreate", async (guild) => { 113 | // creates the text channel 114 | const channel = await guild.channels.create("daily-standups", { 115 | type: "text", 116 | topic: "Scrum Standup Meeting Channel", 117 | }); 118 | 119 | // creates the database model 120 | const newStandup = new standupModel({ 121 | _id: guild.id, 122 | channelId: channel.id, 123 | members: [], 124 | responses: new Map(), 125 | }); 126 | 127 | newStandup 128 | .save() 129 | .then(() => console.log("Howdy!")) 130 | .catch((err) => console.error(err)); 131 | 132 | await channel.send(standupIntroMessage); 133 | }); 134 | 135 | // delete the mongodb entry 136 | bot.on("guildDelete", (guild) => { 137 | standupModel 138 | .findByIdAndDelete(guild.id) 139 | .then(() => console.log("Peace!")) 140 | .catch((err) => console.error(err)); 141 | }); 142 | 143 | /** 144 | * Cron Job: 10:30:00 AM EST - Go through each standup and output the responses to the channel 145 | */ 146 | let cron = schedule.scheduleJob( 147 | { hour: 15, minute: 30, dayOfWeek: new schedule.Range(1, 5) }, 148 | (time) => { 149 | console.log(`[${time}] - CRON JOB START`); 150 | standupModel 151 | .find() 152 | .then((standups) => { 153 | standups.forEach((standup) => { 154 | let memberResponses = []; 155 | let missingMembers = []; 156 | standup.members.forEach((id) => { 157 | if (standup.responses.has(id)) { 158 | memberResponses.push({ 159 | name: `-`, 160 | value: `<@${id}>\n${standup.responses.get(id)}`, 161 | }); 162 | standup.responses.delete(id); 163 | } else { 164 | missingMembers.push(id); 165 | } 166 | }); 167 | let missingString = "Hooligans: "; 168 | if (!missingMembers.length) missingString += ":man_shrugging:"; 169 | else missingMembers.forEach((id) => (missingString += `<@${id}> `)); 170 | bot.channels.cache 171 | .get(standup.channelId) 172 | .send( 173 | new MessageEmbed(dailyStandupSummary) 174 | .setDescription(missingString) 175 | .addFields(memberResponses) 176 | ); 177 | standup 178 | .save() 179 | .then(() => 180 | console.log(`[${new Date()}] - ${standup._id} RESPONSES CLEARED`) 181 | ) 182 | .catch((err) => console.error(err)); 183 | }); 184 | }) 185 | .catch((err) => console.error(err)); 186 | } 187 | ); 188 | 189 | bot.login(process.env.DISCORD_TOKEN); 190 | -------------------------------------------------------------------------------- /models/standup.model.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | /** 4 | * Schema for standup 5 | * 6 | * @property {String} _id id of the guild 7 | * @property {String} channelId id of the text-channel 'daily-standups' 8 | * @property {[String]} members array of userIds for the members of the standup 9 | * @property {Map} responses Map of responses 10 | */ 11 | const standupSchema = new Schema({ 12 | _id: String, 13 | channelId: String, 14 | members: [String], 15 | responses: { 16 | type: Map, 17 | of: String, 18 | }, 19 | }); 20 | 21 | /** 22 | * Checks if all members have posted in the standup 23 | * @param {function} callback 24 | */ 25 | standupSchema.methods.checkFulfilled = function (callback) { 26 | const missing = []; 27 | 28 | this.members.forEach((member) => { 29 | if (!this.responses.get(member)) { 30 | missing.push(member); 31 | } 32 | }); 33 | callback(missing); 34 | }; 35 | 36 | module.exports = model("Standup", standupSchema); 37 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "standup-bot", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@discordjs/collection": { 8 | "version": "0.1.6", 9 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", 10 | "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" 11 | }, 12 | "@discordjs/form-data": { 13 | "version": "3.0.1", 14 | "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", 15 | "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", 16 | "requires": { 17 | "asynckit": "^0.4.0", 18 | "combined-stream": "^1.0.8", 19 | "mime-types": "^2.1.12" 20 | } 21 | }, 22 | "abort-controller": { 23 | "version": "3.0.0", 24 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 25 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 26 | "requires": { 27 | "event-target-shim": "^5.0.0" 28 | } 29 | }, 30 | "asynckit": { 31 | "version": "0.4.0", 32 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 33 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 34 | }, 35 | "bl": { 36 | "version": "2.2.1", 37 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 38 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 39 | "requires": { 40 | "readable-stream": "^2.3.5", 41 | "safe-buffer": "^5.1.1" 42 | } 43 | }, 44 | "bluebird": { 45 | "version": "3.5.1", 46 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 47 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 48 | }, 49 | "bson": { 50 | "version": "1.1.5", 51 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 52 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 53 | }, 54 | "combined-stream": { 55 | "version": "1.0.8", 56 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 57 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 58 | "requires": { 59 | "delayed-stream": "~1.0.0" 60 | } 61 | }, 62 | "core-util-is": { 63 | "version": "1.0.2", 64 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 65 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 66 | }, 67 | "cron-parser": { 68 | "version": "2.16.3", 69 | "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.16.3.tgz", 70 | "integrity": "sha512-XNJBD1QLFeAMUkZtZQuncAAOgJFWNhBdIbwgD22hZxrcWOImBFMKgPC66GzaXpyoJs7UvYLLgPH/8BRk/7gbZg==", 71 | "requires": { 72 | "is-nan": "^1.3.0", 73 | "moment-timezone": "^0.5.31" 74 | } 75 | }, 76 | "debug": { 77 | "version": "3.1.0", 78 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 79 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 80 | "requires": { 81 | "ms": "2.0.0" 82 | }, 83 | "dependencies": { 84 | "ms": { 85 | "version": "2.0.0", 86 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 87 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 88 | } 89 | } 90 | }, 91 | "define-properties": { 92 | "version": "1.1.3", 93 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 94 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 95 | "requires": { 96 | "object-keys": "^1.0.12" 97 | } 98 | }, 99 | "delayed-stream": { 100 | "version": "1.0.0", 101 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 102 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 103 | }, 104 | "denque": { 105 | "version": "1.4.1", 106 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 107 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 108 | }, 109 | "discord.js": { 110 | "version": "12.3.1", 111 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.3.1.tgz", 112 | "integrity": "sha512-mSFyV/mbvzH12UXdS4zadmeUf8IMQOo/YdunubG1wWt1xjWvtaJz/s9CGsFD2B5pTw1W/LXxxUbrQjIZ/xlUdw==", 113 | "requires": { 114 | "@discordjs/collection": "^0.1.6", 115 | "@discordjs/form-data": "^3.0.1", 116 | "abort-controller": "^3.0.0", 117 | "node-fetch": "^2.6.0", 118 | "prism-media": "^1.2.2", 119 | "setimmediate": "^1.0.5", 120 | "tweetnacl": "^1.0.3", 121 | "ws": "^7.3.1" 122 | } 123 | }, 124 | "dotenv": { 125 | "version": "8.2.0", 126 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 127 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 128 | }, 129 | "event-target-shim": { 130 | "version": "5.0.1", 131 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 132 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 133 | }, 134 | "inherits": { 135 | "version": "2.0.4", 136 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 137 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 138 | }, 139 | "is-nan": { 140 | "version": "1.3.0", 141 | "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.0.tgz", 142 | "integrity": "sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==", 143 | "requires": { 144 | "define-properties": "^1.1.3" 145 | } 146 | }, 147 | "isarray": { 148 | "version": "1.0.0", 149 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 150 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 151 | }, 152 | "kareem": { 153 | "version": "2.3.1", 154 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 155 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 156 | }, 157 | "long-timeout": { 158 | "version": "0.1.1", 159 | "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", 160 | "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" 161 | }, 162 | "memory-pager": { 163 | "version": "1.5.0", 164 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 165 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 166 | "optional": true 167 | }, 168 | "mime-db": { 169 | "version": "1.44.0", 170 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 171 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 172 | }, 173 | "mime-types": { 174 | "version": "2.1.27", 175 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 176 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 177 | "requires": { 178 | "mime-db": "1.44.0" 179 | } 180 | }, 181 | "moment": { 182 | "version": "2.29.1", 183 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 184 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" 185 | }, 186 | "moment-timezone": { 187 | "version": "0.5.31", 188 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", 189 | "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", 190 | "requires": { 191 | "moment": ">= 2.9.0" 192 | } 193 | }, 194 | "mongodb": { 195 | "version": "3.6.2", 196 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz", 197 | "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==", 198 | "requires": { 199 | "bl": "^2.2.1", 200 | "bson": "^1.1.4", 201 | "denque": "^1.4.1", 202 | "require_optional": "^1.0.1", 203 | "safe-buffer": "^5.1.2", 204 | "saslprep": "^1.0.0" 205 | } 206 | }, 207 | "mongoose": { 208 | "version": "5.10.8", 209 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.8.tgz", 210 | "integrity": "sha512-hbpFhOU6rWkWPkekUeSJxqWwzsjVQZ9xPg4WmWA1HJ8YDvjyNye1xbp82fw67BpnyvcjHxyU3/YhujsOCx55yw==", 211 | "requires": { 212 | "bson": "^1.1.4", 213 | "kareem": "2.3.1", 214 | "mongodb": "3.6.2", 215 | "mongoose-legacy-pluralize": "1.0.2", 216 | "mpath": "0.7.0", 217 | "mquery": "3.2.2", 218 | "ms": "2.1.2", 219 | "regexp-clone": "1.0.0", 220 | "safe-buffer": "5.2.1", 221 | "sift": "7.0.1", 222 | "sliced": "1.0.1" 223 | } 224 | }, 225 | "mongoose-legacy-pluralize": { 226 | "version": "1.0.2", 227 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 228 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 229 | }, 230 | "mpath": { 231 | "version": "0.7.0", 232 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", 233 | "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" 234 | }, 235 | "mquery": { 236 | "version": "3.2.2", 237 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 238 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 239 | "requires": { 240 | "bluebird": "3.5.1", 241 | "debug": "3.1.0", 242 | "regexp-clone": "^1.0.0", 243 | "safe-buffer": "5.1.2", 244 | "sliced": "1.0.1" 245 | }, 246 | "dependencies": { 247 | "safe-buffer": { 248 | "version": "5.1.2", 249 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 250 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 251 | } 252 | } 253 | }, 254 | "ms": { 255 | "version": "2.1.2", 256 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 257 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 258 | }, 259 | "node-fetch": { 260 | "version": "2.6.1", 261 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 262 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 263 | }, 264 | "node-schedule": { 265 | "version": "1.3.2", 266 | "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.2.tgz", 267 | "integrity": "sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==", 268 | "requires": { 269 | "cron-parser": "^2.7.3", 270 | "long-timeout": "0.1.1", 271 | "sorted-array-functions": "^1.0.0" 272 | } 273 | }, 274 | "object-keys": { 275 | "version": "1.1.1", 276 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 277 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 278 | }, 279 | "prism-media": { 280 | "version": "1.2.2", 281 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", 282 | "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==" 283 | }, 284 | "process-nextick-args": { 285 | "version": "2.0.1", 286 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 287 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 288 | }, 289 | "readable-stream": { 290 | "version": "2.3.7", 291 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 292 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 293 | "requires": { 294 | "core-util-is": "~1.0.0", 295 | "inherits": "~2.0.3", 296 | "isarray": "~1.0.0", 297 | "process-nextick-args": "~2.0.0", 298 | "safe-buffer": "~5.1.1", 299 | "string_decoder": "~1.1.1", 300 | "util-deprecate": "~1.0.1" 301 | }, 302 | "dependencies": { 303 | "safe-buffer": { 304 | "version": "5.1.2", 305 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 306 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 307 | } 308 | } 309 | }, 310 | "regexp-clone": { 311 | "version": "1.0.0", 312 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 313 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 314 | }, 315 | "require_optional": { 316 | "version": "1.0.1", 317 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 318 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 319 | "requires": { 320 | "resolve-from": "^2.0.0", 321 | "semver": "^5.1.0" 322 | } 323 | }, 324 | "resolve-from": { 325 | "version": "2.0.0", 326 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 327 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 328 | }, 329 | "safe-buffer": { 330 | "version": "5.2.1", 331 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 332 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 333 | }, 334 | "saslprep": { 335 | "version": "1.0.3", 336 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 337 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 338 | "optional": true, 339 | "requires": { 340 | "sparse-bitfield": "^3.0.3" 341 | } 342 | }, 343 | "semver": { 344 | "version": "5.7.1", 345 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 346 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 347 | }, 348 | "setimmediate": { 349 | "version": "1.0.5", 350 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 351 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 352 | }, 353 | "sift": { 354 | "version": "7.0.1", 355 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 356 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 357 | }, 358 | "sliced": { 359 | "version": "1.0.1", 360 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 361 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 362 | }, 363 | "sorted-array-functions": { 364 | "version": "1.3.0", 365 | "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", 366 | "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" 367 | }, 368 | "sparse-bitfield": { 369 | "version": "3.0.3", 370 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 371 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 372 | "optional": true, 373 | "requires": { 374 | "memory-pager": "^1.0.2" 375 | } 376 | }, 377 | "string_decoder": { 378 | "version": "1.1.1", 379 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 380 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 381 | "requires": { 382 | "safe-buffer": "~5.1.0" 383 | }, 384 | "dependencies": { 385 | "safe-buffer": { 386 | "version": "5.1.2", 387 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 388 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 389 | } 390 | } 391 | }, 392 | "tweetnacl": { 393 | "version": "1.0.3", 394 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 395 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" 396 | }, 397 | "util-deprecate": { 398 | "version": "1.0.2", 399 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 400 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 401 | }, 402 | "ws": { 403 | "version": "7.3.1", 404 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 405 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" 406 | } 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "standup-bot", 3 | "version": "1.0.0", 4 | "description": "Discord bot for scrum daily standups", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/navn-r/standup-bot.git" 13 | }, 14 | "author": "Navinn Ravindaran", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/navn-r/standup-bot/issues" 18 | }, 19 | "homepage": "https://github.com/navn-r/standup-bot#readme", 20 | "dependencies": { 21 | "discord.js": "^12.3.1", 22 | "dotenv": "^8.2.0", 23 | "mongoose": "^5.10.8", 24 | "node-schedule": "^1.3.2" 25 | } 26 | } 27 | --------------------------------------------------------------------------------