├── .gitignore ├── LICENSE ├── README.md ├── actions.js ├── activation-strings.js ├── application-questions.js ├── index.js ├── package-lock.json ├── package.json └── strings.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | auth.json 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Saul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discord-application-bot 2 | A simple application bot for discord that allows admins to create application forms for the user to complete. 3 | 4 | ## Usage 5 | 6 | ### Running 7 | 1. Create a file in this directory called "auth.json" and put in it "{"token": "YOUR-AUTH-TOKEN"}" 8 | 2. Run with `npm run bot` 9 | 10 | ### Server Setup 11 | 1. Add the role `Admin` and add this role to the admins. 12 | 2. Message `$setsubmissions` in your server to tell the bot to submit to you (or alternatively use the `$setsubmissionschannel` command). 13 | 3. Message `$setup` to configure the application form. 14 | 4. Message `$endsetup` to finish. 15 | 16 | ### Applying 17 | End users need to run `$apply` to start an application. 18 | 19 | ## Customising 20 | 21 | ### Command Symbol 22 | To add or change the symbol (or string) that goes before the command edit `activation-strings.js` and add or remove strings from the array. 23 | 24 | ### Messages 25 | To change what the bot says change the appropriate text in `strings.js` 26 | 27 | ### Default Questions 28 | To change the default application questions that are set when the bot starts up edit the `application-questions.js` file. 29 | 30 | ### Extending Functionality 31 | To add a command to this bot add a function to the `module.exports` object in `actions.js`.
32 | The function needs to be named the command you want it to listen for and it will call that with the message passed as a parameter. 33 | -------------------------------------------------------------------------------- /actions.js: -------------------------------------------------------------------------------- 1 | const strings = require("./strings.js"); 2 | const activationStrings = require("./activation-strings.js"); 3 | 4 | let applicationQuestions = require("./application-questions.js"); 5 | 6 | let isSettingFormUp = false; 7 | let appNewForm = []; 8 | let usersApplicationStatus = []; 9 | let userToSubmitApplicationsTo = null; 10 | 11 | const authorAuthorization = msg => { 12 | const authorId = msg.author.id; 13 | 14 | const role = msg.guild.roles.cache.find(role => role.name.toLowerCase() === strings.adminRole.toLowerCase()); 15 | 16 | const guildMember = msg.guild.members.cache.find(member => member.id === authorId); 17 | 18 | if (!role) { 19 | msg.reply(strings.unknownRole); 20 | return false; 21 | } 22 | 23 | const roleFromUser = guildMember.roles.cache.get(role.id); 24 | 25 | if (!roleFromUser) { 26 | msg.reply(strings.notAuth); 27 | return false; 28 | } 29 | 30 | return true; 31 | }; 32 | 33 | const applicationFormCompleted = (data) => { 34 | let i = 0, answers = ""; 35 | 36 | for (; i < applicationQuestions.length; i++) { 37 | answers += `${applicationQuestions[i]}: ${data.answers[i]}\n`; 38 | } 39 | 40 | if (userToSubmitApplicationsTo) { 41 | const userSubmitString = strings.formReceiveMessage({ 42 | user: data.user.username, 43 | botChar: activationStrings[0] 44 | }); 45 | 46 | userToSubmitApplicationsTo.send(`${userSubmitString}\n${answers}`); 47 | } 48 | }; 49 | 50 | const cancelUserApplicationForm = (msg, isRedo = false) => { 51 | const user = usersApplicationStatus.find(user => user.id === msg.author.id); 52 | 53 | if (user) { 54 | usersApplicationStatus = usersApplicationStatus.filter(el => el.id !== user.id) 55 | msg.reply(strings.applicationCancel); 56 | } else if (!isRedo) { 57 | msg.reply(strings.applicationFormFalseCancel); 58 | } 59 | }; 60 | 61 | const sendUserApplyForm = msg => { 62 | const user = usersApplicationStatus.find(user => user.id === msg.author.id); 63 | 64 | if (!user) { 65 | const userApplyString = strings.formApplyMessage({ 66 | user: msg.author.username, 67 | botChar: activationStrings[0] 68 | }); 69 | 70 | msg.author.send(userApplyString); 71 | msg.author.send(applicationQuestions[0]); 72 | usersApplicationStatus.push({id: msg.author.id, currentStep: 0, answers: [], user: msg.author}); 73 | } else { 74 | msg.author.send(applicationQuestions[user.currentStep]); 75 | } 76 | }; 77 | 78 | module.exports = { 79 | directMessage: msg => { 80 | if (msg.author.id === isSettingFormUp) { 81 | appNewForm.push(msg.content); 82 | } else { 83 | const user = usersApplicationStatus.find(user => user.id === msg.author.id); 84 | 85 | if (user && msg.content) { 86 | user.answers.push(msg.content); 87 | user.currentStep++; 88 | 89 | if (user.currentStep >= applicationQuestions.length) { 90 | usersApplicationStatus = usersApplicationStatus.filter(item => item.id != user.id); 91 | 92 | if (!userToSubmitApplicationsTo) { 93 | msg.author.send(strings.submissionsNotSet); 94 | return; 95 | } 96 | 97 | applicationFormCompleted(user); 98 | 99 | msg.author.send(strings.applicationSent); 100 | } else { 101 | msg.author.send(applicationQuestions[user.currentStep]); 102 | } 103 | } 104 | } 105 | }, 106 | 107 | setup: msg => { 108 | if (!msg.guild) { 109 | msg.reply(strings.notInGuild); 110 | return; 111 | } 112 | 113 | if (!authorAuthorization(msg)) 114 | return; 115 | 116 | if (isSettingFormUp) { 117 | msg.reply(strings.formSetupInProgress); 118 | return; 119 | } 120 | 121 | appNewForm = []; 122 | isSettingFormUp = msg.author.id; 123 | 124 | const adminSetupString = strings.formSetupMessage({ 125 | user: msg.author.username, 126 | botChar: activationStrings[0] 127 | }); 128 | 129 | msg.author.send(adminSetupString); 130 | }, 131 | 132 | endsetup: msg => { 133 | if (isSettingFormUp !== msg.author.id) { 134 | msg.reply(strings.formSetupInProgress); 135 | return; 136 | } 137 | 138 | applicationQuestions = appNewForm; 139 | 140 | isSettingFormUp = false; 141 | appNewForm = []; 142 | 143 | msg.reply(strings.newFormSetup); 144 | }, 145 | 146 | setsubmissions: msg => { 147 | if (!msg.guild) { 148 | msg.reply(strings.notInGuild); 149 | return; 150 | } 151 | 152 | if (!authorAuthorization(msg)) 153 | return; 154 | 155 | userToSubmitApplicationsTo = msg.author; 156 | 157 | msg.reply(strings.setSubmissionsReply); 158 | }, 159 | 160 | setsubmissionschannel: msg => { 161 | if (!msg.guild) { 162 | msg.reply(strings.notInGuild); 163 | return; 164 | } 165 | 166 | if (!authorAuthorization(msg)) 167 | return; 168 | 169 | userToSubmitApplicationsTo = msg.channel; 170 | 171 | msg.reply(strings.setSubmissionsChannelReply); 172 | }, 173 | 174 | apply: msg => { 175 | sendUserApplyForm(msg); 176 | }, 177 | 178 | cancel: msg => { 179 | cancelUserApplicationForm(msg); 180 | }, 181 | 182 | redo: msg => { 183 | cancelUserApplicationForm(msg, true); 184 | sendUserApplyForm(msg); 185 | } 186 | }; 187 | -------------------------------------------------------------------------------- /activation-strings.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | "$" 3 | ]; 4 | -------------------------------------------------------------------------------- /application-questions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | "question 1", 3 | "question 2", 4 | "question 3" 5 | ]; 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // https://discordapp.com/oauth2/authorize?client_id=479944525943537665&scope=bot&permissions=268528647 2 | 3 | const Discord = require('discord.js'); 4 | const auth = require("./auth.json"); 5 | const activationStrings = require("./activation-strings.js"); 6 | const actions = require("./actions.js"); 7 | const strings = require("./strings.js") 8 | 9 | const client = new Discord.Client(); 10 | 11 | client.on('ready', () => { 12 | console.log(`Logged in as ${client.user.tag}!`); 13 | }) 14 | 15 | client.on('message', msg => { 16 | let hasRanCommand = false; 17 | 18 | activationStrings.forEach(str => { 19 | const strLen = str.length; 20 | 21 | if (msg.content.substr(0, strLen) === str) { 22 | const command = msg.content.substr(strLen); 23 | 24 | hasRanCommand = true; 25 | 26 | if (!actions[command]) 27 | msg.reply(strings.unknownCommand); 28 | 29 | try { 30 | actions[command](msg); 31 | } catch (e) { 32 | msg.reply(strings.error); 33 | console.error(e); 34 | } 35 | } 36 | }); 37 | 38 | if (!hasRanCommand && msg.channel.type === "dm") { 39 | try { 40 | actions.directMessage(msg); 41 | } catch (e) { 42 | msg.reply(strings.error); 43 | console.error(e); 44 | } 45 | } 46 | }); 47 | 48 | client.login(auth.token); 49 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-application-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 | "combined-stream": { 36 | "version": "1.0.8", 37 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 38 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 39 | "requires": { 40 | "delayed-stream": "~1.0.0" 41 | } 42 | }, 43 | "delayed-stream": { 44 | "version": "1.0.0", 45 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 46 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 47 | }, 48 | "discord.js": { 49 | "version": "12.5.1", 50 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", 51 | "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", 52 | "requires": { 53 | "@discordjs/collection": "^0.1.6", 54 | "@discordjs/form-data": "^3.0.1", 55 | "abort-controller": "^3.0.0", 56 | "node-fetch": "^2.6.1", 57 | "prism-media": "^1.2.2", 58 | "setimmediate": "^1.0.5", 59 | "tweetnacl": "^1.0.3", 60 | "ws": "^7.3.1" 61 | } 62 | }, 63 | "event-target-shim": { 64 | "version": "5.0.1", 65 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 66 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 67 | }, 68 | "mime-db": { 69 | "version": "1.45.0", 70 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", 71 | "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" 72 | }, 73 | "mime-types": { 74 | "version": "2.1.28", 75 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", 76 | "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", 77 | "requires": { 78 | "mime-db": "1.45.0" 79 | } 80 | }, 81 | "node-fetch": { 82 | "version": "2.6.1", 83 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 84 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 85 | }, 86 | "prism-media": { 87 | "version": "1.2.3", 88 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", 89 | "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" 90 | }, 91 | "setimmediate": { 92 | "version": "1.0.5", 93 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 94 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 95 | }, 96 | "tweetnacl": { 97 | "version": "1.0.3", 98 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 99 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" 100 | }, 101 | "ws": { 102 | "version": "7.4.2", 103 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", 104 | "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-application-bot", 3 | "version": "1.0.0", 4 | "description": "A custom application bot for discord.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "discord.js": "^12.5.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "bot": "node index.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/saul-avikar/discord-application-bot.git" 16 | }, 17 | "author": "saul ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/saul-avikar/discord-application-bot/issues" 21 | }, 22 | "homepage": "https://github.com/saul-avikar/discord-application-bot#readme" 23 | } 24 | -------------------------------------------------------------------------------- /strings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | unknownCommand: "I could not run that command.", 3 | notInGuild: "This command can only be used in a guild.", 4 | notAuth: "This command can only be used by an admin.", 5 | error: "I could not run that command, please contact your server administrator.", 6 | adminRole: "Admin", 7 | unknownRole: "This server is missing the admin role, please contact your server administrator.", 8 | notSettingUpEnd: "You have not setup the form.", 9 | setSubmissionsReply: "Form submissions will now be sent to you.", 10 | setSubmissionsChannelReply: "Form submissions will now be posted in this channel.", 11 | submissionsNotSet: "Submissions have not been set, please contact your server administrator", 12 | applicationCancel: "Application canceled.", 13 | applicationFormFalseCancel: "You have not started an application form yet.", 14 | applicationSent: "Congratulations your application has been sent!", 15 | formSetupInProgress: "Someone else is already configuring the form.", 16 | newFormSetup: "The new form has been setup.", 17 | formReceiveMessage: params => `${params.user} has submitted a form.`, 18 | formApplyMessage: params => `Application commands: \`\`\`${params.botChar}cancel, ${params.botChar}redo\`\`\``, 19 | formSetupMessage: params => `Enter questions and enter \`${params.botChar}endsetup\` when done.` 20 | } 21 | --------------------------------------------------------------------------------