├── .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 |
--------------------------------------------------------------------------------