├── .gitignore ├── database └── database.sqlite ├── src ├── structures │ ├── BaseEvent.js │ └── BaseCommand.js ├── index.js ├── events │ ├── ready.js │ └── message.js ├── models │ └── apps.js ├── modules │ ├── Database.js │ └── ErrorHandler.js ├── commands │ ├── ping.js │ ├── help.js │ ├── reject.js │ ├── accept.js │ └── apply.js ├── utils │ └── Logger.js └── classes │ └── AppBot.js ├── .github └── dependabot.yml ├── config.json ├── config ├── messages.json └── application.json ├── templates └── @command.js ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .env 3 | package-lock.json -------------------------------------------------------------------------------- /database/database.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oadpoaw/app-bot/HEAD/database/database.sqlite -------------------------------------------------------------------------------- /src/structures/BaseEvent.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = class BaseEvent { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /src/structures/BaseCommand.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = class BaseCommand { 3 | constructor(commandName, options) { 4 | this.name = commandName; 5 | this.options = options; 6 | } 7 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const AppBot = require('./classes/AppBot'); 3 | const client = new AppBot.Client(); 4 | 5 | (async function() { 6 | await client.registerCommands('../commands/'); 7 | await client.registerEvents('../events/'); 8 | client.login(process.env.BOT_TOKEN); 9 | })(); -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "tracing": true, 4 | "debug": true, 5 | "warnings": true 6 | }, 7 | "botsettings": { 8 | "managerRoleID": "manager role id who can accept/reject/delete applications", 9 | "logChannelID": "the log channel id where applications are being logged", 10 | "category": "the category where applications are being viewed/sent" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/events/ready.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require('../structures/BaseEvent'); 2 | 3 | const { Client } = require('../classes/AppBot'); 4 | 5 | module.exports = class Ready extends BaseEvent { 6 | constructor() { 7 | super('ready'); 8 | } 9 | /** 10 | * 11 | * @param {Client} client 12 | */ 13 | async run(client) { 14 | client.logger.info(`Bot Ready as ${client.user.tag} / ${client.user.id}`); 15 | } 16 | } -------------------------------------------------------------------------------- /src/models/apps.js: -------------------------------------------------------------------------------- 1 | const { DataTypes } = require('sequelize'); 2 | 3 | module.exports = (sequelize) => { 4 | return sequelize.define('applications', { 5 | app_type: { 6 | type: DataTypes.STRING, 7 | primaryKey: true, 8 | unique: true, 9 | }, 10 | applicants: { 11 | type: DataTypes.JSON, 12 | allowNull: false, 13 | defaultValue: [], 14 | }, 15 | }, { 16 | timestamps: false, 17 | }); 18 | } -------------------------------------------------------------------------------- /src/modules/Database.js: -------------------------------------------------------------------------------- 1 | 2 | const { Sequelize, Transaction } = require('sequelize'); 3 | const { settings } = require('../../config.json'); 4 | 5 | class Database extends Sequelize { 6 | constructor() { 7 | let logger = settings.debug ? console.log : false; 8 | super('database', 'username', 'password', { 9 | host: 'localhost', 10 | dialect: 'sqlite', 11 | logging: logger, 12 | storage: './database/database.sqlite', 13 | transactionType: Transaction.TYPES.IMMEDIATE, 14 | retry: { 15 | max: 10, 16 | } 17 | }); 18 | } 19 | } 20 | 21 | module.exports = Database; -------------------------------------------------------------------------------- /src/modules/ErrorHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = async (client) => { 2 | process.on('unhandledRejection', (err) => { 3 | const errorMsg = err.stack.replace(new RegExp(`${__dirname}/`, 'g'), './'); 4 | client.logger.error(`Unhandled Rejection: ${errorMsg}`); 5 | }); 6 | process.on('warning', (err) => { 7 | const errorMsg = err.stack.replace(new RegExp(`${__dirname}/`, 'g'), './'); 8 | client.logger.warn(errorMsg); 9 | }); 10 | process.on('uncaughtException', (err) => { 11 | const errorMsg = err.stack.replace(new RegExp(`${__dirname}/`, 'g'), './'); 12 | client.logger.error(`Uncaught Exception: ${errorMsg}`); 13 | process.exit(1); 14 | }); 15 | } -------------------------------------------------------------------------------- /config/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandthrottle": "⏲️ Slow down! Please wait **{COOLDOWN}** to use this command again.", 3 | "missingPermissions": "`Error`You do not have the permission to execute this command.", 4 | "clientMissingPermissions": "`Error`: Missing Bot Permissions\n```xl\n{PERMS}\n```", 5 | "missingArguments": "`Error`: Missing Arguments\nThe proper usage would be\n```xl\n{USAGE}\n```", 6 | "commandError": "`Error`: Unexpected error occured : {ERRORNAME} \n```xl\n{ERROR}\n```", 7 | "invalidAppType": "`Error`: Invalid Application type, to see types use `{PREFIX}apptypes`", 8 | "already": "`Error`: You already applied for a(n) `{TYPE}` application.", 9 | "wrongChannel": "`Error`: You cannot apply in this channel." 10 | } -------------------------------------------------------------------------------- /templates/@command.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | const { Client } = require('../classes/AppBot'); 3 | const { Message } = require('discord.js'); 4 | 5 | module.exports = class extends BaseCommand { 6 | constructor() { 7 | super('name', { 8 | aliases: [], 9 | clientPermissions: [], 10 | cooldown: 3, 11 | usage: '', 12 | args: false, 13 | argsDefinitions: { 14 | 15 | } 16 | }) 17 | } 18 | /** 19 | * 20 | * @param {Client} client 21 | * @param {Message} message 22 | * @param {Array|JSON} args 23 | */ 24 | async execute(client, message, args) { 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node .", 8 | "test": "node .", 9 | "reset-database": "node . --force" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/oadpoaw/app-bot.git" 14 | }, 15 | "author": "undefine#6084", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/oadpoaw/app-bot/issues" 19 | }, 20 | "homepage": "https://github.com/oadpoaw/app-bot#readme", 21 | "dependencies": { 22 | "chalk": "^4.1.0", 23 | "discord.js": "^12.3.1", 24 | "dotenv": "^8.2.0", 25 | "minimist": "^1.2.5", 26 | "minimist-options": "^4.1.0", 27 | "moment": "^2.26.0", 28 | "ms": "^2.1.2", 29 | "sequelize": "^6.3.5", 30 | "sqlite3": "^5.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/ping.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | 3 | const { Client } = require('../classes/AppBot'); 4 | 5 | const { Message } = require('discord.js'); 6 | 7 | module.exports = class Ping extends BaseCommand { 8 | constructor() { 9 | super('ping', { 10 | aliases: ['botping'], 11 | cooldown: 20, 12 | }); 13 | } 14 | /** 15 | * 16 | * @param {Client} client 17 | * @param {Message} message 18 | * @param {Array|JSON} args 19 | */ 20 | async execute(client, message, args) { 21 | return message.channel.send('Ping?').then(msg => { 22 | msg.edit(`Pong! Latency is \`${msg.createdTimestamp - message.createdTimestamp}\`ms.\nAPI Latency is \`${Math.round(client.ws.ping)}ms\``); 23 | }).catch((e) => { 24 | client.logger.error(`Error : ${e}`); 25 | return false; 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 undefine 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 | -------------------------------------------------------------------------------- /src/utils/Logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const moment = require('moment'); 3 | 4 | const { settings } = require('../../config.json'); 5 | 6 | module.exports = { 7 | timestamp: function (thread = 'Server') { 8 | return `[${moment().format('YYYY-MM-DD HH:mm:ss')}] [${thread} Thread]`; 9 | }, 10 | info: function (content, thread = 'Server') { 11 | content = content.replace(new RegExp(`${__dirname}/`, 'g'), './'); 12 | console.log(`${this.timestamp(thread)} ${chalk.black.bgWhite('[INFO]')} : ${content}`); 13 | }, 14 | error: function (content, thread = 'Server') { 15 | content = content.replace(new RegExp(`${__dirname}/`, 'g'), './'); 16 | console.log(`${this.timestamp(thread)} ${chalk.black.bgRed('[ERROR]')} : ${content}`); 17 | if (settings['tracing']) { 18 | console.log(chalk.black.bgRed('[ERROR_TRACE]')); 19 | console.trace(content); 20 | console.log(chalk.black.bgRed('[/ERROR_TRACE]')); 21 | } 22 | }, 23 | warn: function (content, thread = 'Server') { 24 | content = content.replace(new RegExp(`${__dirname}/`, 'g'), './'); 25 | if (settings['warnings']) { 26 | console.log(`${this.timestamp(thread)} ${chalk.black.bgYellow('[WARNING]')} : ${content}`); 27 | if (settings['tracing']) { 28 | console.log(chalk.black.bgYellow('[WARNING_TRACE]')); 29 | console.trace(content); 30 | console.log(chalk.black.bgYellow('[/WARNING_TRACE]')); 31 | } 32 | } 33 | }, 34 | debug: function (content, thread = 'Server') { 35 | content = content.replace(new RegExp(`${__dirname}/`, 'g'), './'); 36 | if (settings['debug']) { 37 | console.log(`${this.timestamp(thread)} ${chalk.black.bgGreen('[DEBUG]')} : ${content}`); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to AppBot 👋

2 |

3 | Version 4 | 5 | Documentation 6 | 7 | 8 | Maintenance 9 | 10 | 11 | License: MIT 12 | 13 |

14 | 15 | > simple application bot for discord 16 | 17 | ### 🏠 [Homepage](https://github.com/oadpoaw/app-bot#readme) 18 | 19 | ## Install 20 | 21 | ```sh 22 | git clone https://github.com/oadpoaw/app-bot.git 23 | cd app-bot 24 | npm install 25 | ``` 26 | 27 | Make an .env file 28 | ``` 29 | BOT_TOKEN=placebottokenheretoworkgotodiscord.com/developers/applicationsokokxdxd 30 | DEFAULT_PREFIX=! 31 | ``` 32 | 33 | Configure it to your will! customizable prompts/questions etc! 34 | - [config.json](config.json) 35 | - [messages.json](config/messages.json) 36 | - [application](config/application.json) 37 | 38 | ## Usage 39 | 40 | ```sh 41 | npm run reset-database # initialize database 42 | npm start # to start the bot 43 | ``` 44 | 45 | ## Commands 46 | 47 | ```sh 48 | apply 49 | help 50 | ping 51 | accept [...Reason] # on the current application channel 52 | deny [...Reason] # on the current application channel 53 | ``` 54 | 55 | ## Run tests 56 | 57 | ```sh 58 | npm run test 59 | ``` 60 | 61 | ## Author 62 | 63 | * Github: [@oadpoaw](https://github.com/oadpoaw) 64 | 65 | ## Show your support 66 | 67 | Give a ⭐️ if this project helped you! 68 | 69 | ## 📝 License 70 | 71 | Copyright © 2020 [undefine#2690](https://github.com/oadpoaw).
72 | This project is [MIT](https://github.com/oadpoaw/app-bot/blob/master/LICENSE) licensed. 73 | -------------------------------------------------------------------------------- /config/application.json: -------------------------------------------------------------------------------- 1 | { 2 | "staff": { 3 | "config": { 4 | "messages": { 5 | "pending": "Your staff application is now pending for approval.", 6 | "accepted": "You've met the requirements and congratulations on your staff position!", 7 | "denied": "You did not met the requirements or unsuitable for the staff position, please try again in 3 months i guess.", 8 | "ongoing": "Your staff application interview is now on going in {CHANNEL}" 9 | } 10 | }, 11 | "questions": [ 12 | "What is your IGN (in game name)?", 13 | "What sub-server do you wish to apply for? eg. Skyblock, Prison", 14 | "How old are you?", 15 | "How long have you played on the network?", 16 | "Have you been muted/banned in the last 30 days?", 17 | "How much can you play weekly? eg. 5hr/10h/15h", 18 | "Why do you want to become a staff member? (What motivated you to apply)", 19 | "In which way can you contribute to the staff team?", 20 | "Anything else you would like to add?" 21 | ] 22 | }, 23 | "unban": { 24 | "config": { 25 | "messages": { 26 | "pending": "Your Ban Appeal is now pending for approval.", 27 | "accepted": "You've met the requirements and congratulations you are now unbanned!", 28 | "denied": "You did not met the requirements or not worthy for an appeal, please try again in 3 months i guess", 29 | "ongoing": "Your Ban Appeal interview is now on going in {CHANNEL}" 30 | } 31 | }, 32 | "questions": [ 33 | "What is your IGN (in game name)?", 34 | "On which gamemode did you get banned on and for what", 35 | "Why should we consider your appeal?", 36 | "Do you believe this was a mistake? (If so reply with why otherwise type 'No')", 37 | "Do you feel guilty?", 38 | "Anything else we should be aware of?" 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /src/commands/help.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | const { Client } = require('../classes/AppBot'); 3 | const { Message, MessageEmbed } = require('discord.js'); 4 | const ms = require('ms'); 5 | 6 | module.exports = class extends BaseCommand { 7 | constructor() { 8 | super('help', { 9 | aliases: ['commands'], 10 | clientPermissions: [], 11 | cooldown: 3, 12 | usage: 'help [-c ]', 13 | args: false, 14 | argsDefinitions: { 15 | arguments: 'string', 16 | command: { 17 | type: 'string', 18 | alias: ['c', 'cmd'], 19 | default: '', 20 | } 21 | } 22 | }) 23 | } 24 | /** 25 | * 26 | * @param {Client} client 27 | * @param {Message} message 28 | * @param {Array|JSON} args 29 | */ 30 | async execute(client, message, args) { 31 | const prefix = client.prefix; 32 | if (args.command) { 33 | const command = client.commands.get(args.command) || client.commands.find((c) => c.options.aliases && c.options.aliases.includes(args.command)); 34 | if (!command) { 35 | return message.channel.send('That command does not exist'); 36 | } else { 37 | let embed = new MessageEmbed() 38 | .setColor('RANDOM') 39 | .setTitle(`**${command.name}** Command`) 40 | .addField(`Cooldown`, `${ms(command.options.cooldown * 1000)}`, true) 41 | ; 42 | if (command.options.aliases && command.options.aliases.length !== 0) 43 | embed.addField(`Aliases`, `${command.options.aliases.join(', ')}`, true); 44 | if (command.options.clientPermissions && command.options.clientPermissions.length !== 0) 45 | embed.addField(`Required Bot Permissions`, `\`\`\`xl\n${command.options.clientPermissions.join(' ')}\n\`\`\``, false) 46 | if (command.options.usage) 47 | embed.addField(`Usage`, `\`\`\`xl\n${command.options.usage}\n\`\`\``, false); 48 | return message.channel.send(embed); 49 | } 50 | 51 | } 52 | return message.channel.send(new MessageEmbed() 53 | .setColor('RANDOM') 54 | .setTitle('Commands') 55 | .setDescription(`Prefix: \`${prefix}\`\n\`help [Command]\``) 56 | .addField('Commands', `\`ping\`, \`apply\`, \`help\``) 57 | .addField('Admin Commands', `\`accept\`, \`reject\``) 58 | .setFooter('Powered by undefine') 59 | ); 60 | } 61 | } -------------------------------------------------------------------------------- /src/commands/reject.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | const { Client } = require('../classes/AppBot'); 3 | const { Message, MessageEmbed } = require('discord.js'); 4 | const { botsettings } = require('../../config.json'); 5 | const applications = require('../../config/application.json'); 6 | 7 | module.exports = class extends BaseCommand { 8 | constructor() { 9 | super('reject', { 10 | aliases: [], 11 | clientPermissions: [], 12 | cooldown: 3, 13 | usage: 'reject [...Reason]', 14 | args: false, 15 | }); 16 | } 17 | /** 18 | * 19 | * @param {Client} client 20 | * @param {Message} message 21 | * @param {Array|JSON} args 22 | */ 23 | async execute(client, message, args) { 24 | if (!message.member.roles.cache.has(botsettings.managerRoleID)) return; 25 | if (message.channel.parentID !== botsettings.category) return message.channel.send('This is not an application channel'); 26 | const [type, id] = message.channel.topic.split(/ +/g); 27 | const b = await client.dbModels.apps.findOne({ where: { app_type: type } }).catch(console.log); 28 | const app = applications[type]; 29 | const applicant = await client.users.fetch(id); 30 | let reason = app.config.messages.denied; 31 | if (args && args.length) reason = args.join(' '); 32 | await message.channel.send(new MessageEmbed() 33 | .setColor('RED') 34 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 35 | .setDescription(`${type.toUpperCase()} Application \`${id}\` of ${applicant} has been denied for:\n${reason}`) 36 | .setTimestamp() 37 | ); 38 | const dm = await applicant.createDM(); 39 | dm.send(new MessageEmbed() 40 | .setColor('RED') 41 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 42 | .setTitle(`Your ${type.toUpperCase()} Application on AppBot has been denied`) 43 | .setTimestamp() 44 | .addField('Reason(s)', reason) 45 | ).catch(console.log); 46 | b.applicants = b.applicants.filter((c) => { return c !== id }); 47 | await b.save(); 48 | client.setTimeout(async () => { 49 | await message.channel.delete('Application... Closed'); 50 | const ch = await client.channels.fetch(botsettings.logChannelID); 51 | ch.send(new MessageEmbed() 52 | .setColor('RED') 53 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 54 | .setDescription(`${type.toUpperCase()} Application \`${id}\` of ${applicant} has been denied for:\n${reason}`) 55 | .setTimestamp() 56 | ); 57 | }, 10000); 58 | } 59 | } -------------------------------------------------------------------------------- /src/commands/accept.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | const { Client } = require('../classes/AppBot'); 3 | const { Message, MessageEmbed } = require('discord.js'); 4 | const { botsettings } = require('../../config.json'); 5 | const applications = require('../../config/application.json'); 6 | 7 | module.exports = class extends BaseCommand { 8 | constructor() { 9 | super('accept', { 10 | aliases: [], 11 | clientPermissions: [], 12 | cooldown: 3, 13 | usage: 'accept [...Reason]', 14 | args: false, 15 | }); 16 | } 17 | /** 18 | * 19 | * @param {Client} client 20 | * @param {Message} message 21 | * @param {Array|JSON} args 22 | */ 23 | async execute(client, message, args) { 24 | if (!message.member.roles.cache.has(botsettings.managerRoleID)) return; 25 | if (message.channel.parentID !== botsettings.category) return message.channel.send('This is not an application channel'); 26 | const [type, id] = message.channel.topic.split(/ +/g); 27 | const b = await client.dbModels.apps.findOne({ where: { app_type: type } }).catch(console.log); 28 | const app = applications[type]; 29 | const applicant = await client.users.fetch(id); 30 | let reason = app.config.messages.accepted; 31 | if (args && args.length) reason = args.join(' '); 32 | await message.channel.send(new MessageEmbed() 33 | .setColor(0x0FF00) 34 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 35 | .setDescription(`${type.toUpperCase()} Application \`${id}\` of ${applicant} has been approved for:\n${reason}`) 36 | .setTimestamp() 37 | ); 38 | const dm = await applicant.createDM(); 39 | dm.send(new MessageEmbed() 40 | .setColor(0x00FF00) 41 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 42 | .setTitle(`Your ${type.toUpperCase()} Application on AppBot has been approved`) 43 | .setTimestamp() 44 | .addField('Reason(s)', reason) 45 | ).catch(console.log); 46 | b.applicants = b.applicants.filter((c) => { return c !== id }); 47 | await b.save(); 48 | client.setTimeout(async () => { 49 | await message.channel.delete('Application... Closed'); 50 | const ch = await client.channels.fetch(botsettings.logChannelID); 51 | ch.send(new MessageEmbed() 52 | .setColor(0x0FF00) 53 | .setAuthor(message.author.tag, message.author.displayAvatarURL() || null) 54 | .setDescription(`${type.toUpperCase()} Application \`${id}\` of ${applicant} has been approved for:\n${reason}`) 55 | .setTimestamp() 56 | ); 57 | }, 10000); 58 | } 59 | } -------------------------------------------------------------------------------- /src/events/message.js: -------------------------------------------------------------------------------- 1 | 2 | const { Message, Collection } = require('discord.js'); 3 | const { Client } = require('../classes/AppBot'); 4 | 5 | const BaseEvent = require('../structures/BaseEvent'); 6 | const messages = require('../../config/messages.json'); 7 | 8 | const minimist = require('minimist'); 9 | const options = require('minimist-options'); 10 | const ms = require('ms'); 11 | 12 | /** 13 | * @returns {Object} 14 | * @param {Array} args 15 | * @param {Object} opts 16 | */ 17 | function parseArguments(args, opts) { 18 | return minimist(args, options(opts)); 19 | } 20 | 21 | module.exports = class MessageEvent extends BaseEvent { 22 | constructor() { 23 | super('message') 24 | } 25 | /** 26 | * 27 | * @param {Client} client 28 | * @param {Message} message 29 | */ 30 | async run(client, message) { 31 | if (message.author.bot || message.channel.type !== 'text') return; 32 | if (!message.member) await message.member.fetch(); 33 | const prefix = new RegExp(`^(<@!?${client.user.id}>|${client.escapeRegex(client.prefix)})\\s*`); 34 | if (!prefix.test(message.content)) return; 35 | const [, matchedPrefix] = message.content.match(prefix); 36 | const args = message.content.slice(matchedPrefix.length).trim().split(/ +/); 37 | const commandName = args.shift().toLowerCase(); 38 | const command = client.commands.get(commandName) || client.commands.find(c => c.options.aliases && c.options.aliases.includes(commandName)); 39 | if (!command) return; 40 | if (command.options.clientPermissions && !message.guild.me.permissions.has(command.options.clientPermissions)) return message.channel.send(messages.clientMissingPermissions.replace(/{PERMS}/g, command.options.clientPermissions.join(' '))).then((c) => c.delete({ timeout: 2000 })).catch(console.log); 41 | if (command.options.args && !args.length && command.options.usage) return message.channel.send(messages.missingArguments.replace(/{USAGE}/g, command.options.usage)).then((c) => c.delete({ timeout: 2000 })).catch(console.log); 42 | if (!client.cooldowns.has(command.name)) client.cooldowns.set(command.name, new Collection()); 43 | const now = Date.now(); 44 | const timestamps = client.cooldowns.get(command.name); 45 | const cooldownAmount = (command.options.cooldown || 3) * 1000; 46 | if (timestamps.has(message.author.id)) { 47 | const expirationTime = timestamps.get(message.author.id) + cooldownAmount; 48 | if (now < expirationTime) { 49 | const timeLeft = ms(expirationTime - now); 50 | return message.channel.send(messages.commandthrottle.replace(/{COOLDOWN}/g, timeLeft.toString())).then((c) => c.delete({ timeout: 2000 })).catch(console.log); 51 | } 52 | } 53 | try { 54 | const argv = command.options.argsDefinitions ? parseArguments(args, command.options.argsDefinitions) : args; 55 | const status = await command.execute(client, message, argv); 56 | if (status !== false) { 57 | timestamps.set(message.author.id, now); 58 | client.setTimeout(() => { 59 | timestamps.delete(message.author.id); 60 | }, cooldownAmount); 61 | } 62 | } catch (e) { 63 | message.channel.send(client.trim(messages.commandError.replace(/{ERRORNAME}/g, e.name).replace(/{ERROR}/g, e), 2048)); 64 | } 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /src/commands/apply.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/BaseCommand'); 2 | const { Client } = require('../classes/AppBot'); 3 | const { Message, MessageEmbed } = require('discord.js'); 4 | 5 | const apps = require('../../config/application.json'); 6 | const messages = require('../../config/messages.json'); 7 | const { botsettings } = require('../../config.json'); 8 | 9 | module.exports = class extends BaseCommand { 10 | constructor() { 11 | super('apply', { 12 | aliases: [], 13 | clientPermissions: ['MANAGE_CHANNELS', 'MANAGE_ROLES'], 14 | cooldown: 10, 15 | usage: `apply \nTypes:\nstaff\nunban`, 16 | args: true, 17 | }); 18 | } 19 | /** 20 | * 21 | * @param {Client} client 22 | * @param {Message} message 23 | * @param {Array} args 24 | */ 25 | async execute(client, message, args) { 26 | await message.delete(); 27 | const type = args[0].toLowerCase(); 28 | if (message.channel.parentID === botsettings.category) return message.channel.send(messages.wrongChannel); 29 | if (!apps.hasOwnProperty(type)) return message.channel.send(messages.invalidAppType.replace(/{PREFIX}/g, client.prefix)).then((m) => m.delete({ timeout: 10000 })); 30 | const app = apps[type]; 31 | let db = await client.dbModels.apps.findOne({ where: { app_type: type } }); 32 | if (db && db.applicants && db.applicants.includes(message.author.id)) return message.channel.send(messages.already.replace(/{TYPE}/g, type)).then((m) => m.delete({ timeout: 10000 })); 33 | if (!db) db = await client.dbModels.apps.create({ app_type: type }); 34 | const channel = await message.guild.channels.create(`${type}-${message.author.username}`, { 35 | type: 'text', 36 | parent: botsettings.category, 37 | topic: `${type} ${message.author.id}`, 38 | permissionOverwrites: [ 39 | { 40 | id: message.guild.id, 41 | deny: ['VIEW_CHANNEL'] 42 | }, 43 | { 44 | id: message.author.id, 45 | allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY'] 46 | }, 47 | { 48 | id: client.user.id, 49 | allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY', 'MANAGE_CHANNELS'] 50 | }, 51 | { 52 | id: botsettings.managerRoleID, 53 | allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY', 'MANAGE_CHANNELS'] 54 | } 55 | ] 56 | }); 57 | const msg = await channel.send(`${message.author}'s Interview on going...\n\nType \`${client.prefix}cancel\` to cancel the interview`); 58 | let ApplicationResponse = ''; 59 | for await (const question of app.questions) { 60 | const response = await client.awaitReply(msg, message.author.id, `${question}`, 60000 * 30, false, true, true); 61 | if (!response || response === `${client.prefix}cancel`) { 62 | ApplicationResponse = 'CANCELLED'; 63 | break; 64 | } 65 | ApplicationResponse += `> ${question}\n${response}\n\n`; 66 | } 67 | await msg.delete({ timeout: 1000 }); 68 | if (ApplicationResponse === 'CANCELLED') { 69 | await channel.send('Interview has been cancelled\n\nDeleting this channel in 10 seconds...'); 70 | setTimeout(() => { 71 | channel.delete(); 72 | }, 10000) 73 | return true; 74 | } 75 | const copy = ApplicationResponse; 76 | const content = client.chunkString(copy, 2048); 77 | await channel.send(new MessageEmbed() 78 | .setColor('RANDOM') 79 | .setTimestamp() 80 | .setAuthor(`${message.author.tag}'s ${type} application`, message.author.displayAvatarURL() || null) 81 | ); 82 | for await (const text of content) { 83 | await channel.send(new MessageEmbed() 84 | .setColor('RANDOM') 85 | .setDescription(text) 86 | ); 87 | } 88 | await channel.send(app.config.messages.pending); 89 | if (!db.applicants || !Array.isArray(db.applicants)) db.applicants = []; 90 | db.applicants.push(message.author.id); 91 | await db.save(); 92 | const ch = await client.channels.fetch(botsettings.logChannelID); 93 | await ch.send(new MessageEmbed() 94 | .setColor('RANDOM') 95 | .setTimestamp() 96 | .setAuthor(`${message.author.tag}'s ${type} application`, message.author.displayAvatarURL() || null) 97 | ); 98 | for await (const text of content) { 99 | await ch.send(new MessageEmbed() 100 | .setColor('RANDOM') 101 | .setDescription(text) 102 | ); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/classes/AppBot.js: -------------------------------------------------------------------------------- 1 | const { Client, Collection, Message, TextChannel, MessageEmbed } = require('discord.js'); 2 | const path = require('path'); 3 | const fs = require('fs').promises; 4 | 5 | const BaseCommand = require('../structures/BaseCommand'); 6 | const BaseEvent = require('../structures/BaseEvent'); 7 | const Database = require('../modules/Database'); 8 | const Logger = require('../utils/Logger'); 9 | const apps = require('../models/apps'); 10 | 11 | class AppBotClient extends Client { 12 | constructor() { 13 | super({ 14 | partials: ['MESSAGE', 'REACTION', 'CHANNEL'], 15 | fetchAllMembers: false, 16 | messageCacheMaxSize: 100, 17 | }); 18 | this.prefix = process.env.DEFAULT_PREFIX; 19 | this.commands = new Collection(); 20 | this.cooldowns = new Collection(); 21 | this.logger = Logger; 22 | this.database = new Database(); 23 | this.dbModels = { 24 | apps: apps(this.database), 25 | } 26 | require('../modules/ErrorHandler')(this); 27 | if (process.argv.includes('-f') || process.argv.includes('--force')) { 28 | this.database.sync({ force: true }); 29 | this.logger.info('Database Table has been dropped!'); 30 | process.exit(); 31 | } 32 | this.database.authenticate().then(() => { 33 | this.logger.info('Database Connection Established!'); 34 | }).catch(err => { 35 | this.logger.error(`Unable to connect to the database: ${err} ${JSON.stringify(err)}`); 36 | }); 37 | } 38 | /** 39 | * 40 | * @param {String} dir commands directory 41 | */ 42 | async registerCommands(dir) { 43 | const filePath = path.join(__dirname, dir); 44 | const files = await fs.readdir(filePath); 45 | for (const file of files) { 46 | const Command = require(path.join(filePath, file)); 47 | if (Command.prototype instanceof BaseCommand) { 48 | const instance = new Command(); 49 | this.commands.set(instance.name, instance); 50 | } 51 | } 52 | } 53 | /** 54 | * 55 | * @param {String} dir events category 56 | */ 57 | async registerEvents(dir) { 58 | const filePath = path.join(__dirname, dir); 59 | const files = await fs.readdir(filePath); 60 | for (const file of files) { 61 | const Event = require(path.join(filePath, file)); 62 | if (Event.prototype instanceof BaseEvent) { 63 | const instance = new Event(); 64 | this.on(instance.name, instance.run.bind(instance, this)); 65 | } 66 | } 67 | } 68 | /** 69 | * @returns {Promise|Promise} 70 | * @param {Message|TextChannel} message 71 | * @param {String|MessageEmbed} question 72 | * @param {Number} duration in millieseconds 73 | * @param {Boolean} obj if true, returns the message object collected not message content 74 | */ 75 | async awaitReply(message, author, question, duration = 60000, obj = false, del = false, delr = false) { 76 | const filter = m => m.author.id === author; 77 | const cc = await message.channel.send(question); 78 | try { 79 | const collected = await message.channel.awaitMessages(filter, { max: 1, time: duration, errors: ['time'] }); 80 | if (delr) await collected.first().delete(); 81 | if (del) await cc.delete(); 82 | return obj ? collected.first() : collected.first().content; 83 | } catch (e) { 84 | return false; 85 | } 86 | } 87 | /** 88 | * @returns {String} 89 | * @param {String} str 90 | */ 91 | escapeRegex(str) { 92 | return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 93 | } 94 | /** 95 | * @returns {String} 96 | * @param {String} str 97 | */ 98 | trim(str, max) { 99 | return (str.length > max) ? `${str.slice(0, max - 3)}...` : str; 100 | } 101 | /** 102 | * @returns {Array>} 103 | * @param {Array} array 104 | * @param {Number} chunkSize 105 | */ 106 | chunk(array, chunkSize = 0) { 107 | return array.reduce(function (previous, current) { 108 | let chunk; 109 | if (previous.length === 0 || previous[previous.length - 1].length === chunkSize) { 110 | chunk = []; 111 | previous.push(chunk); 112 | } 113 | else { 114 | chunk = previous[previous.length - 1]; 115 | } 116 | chunk.push(current); 117 | return previous; 118 | }, []); 119 | } 120 | /** 121 | * 122 | * @param {String} str 123 | * @param {Number} length 124 | */ 125 | chunkString(str, length) { 126 | return str.match(new RegExp(`(.|[\r\n]){1,${length}}`, 'g')); 127 | } 128 | /** 129 | * @returns {Promise} 130 | * @param {Promise|String} text 131 | */ 132 | async clean(text) { 133 | if (text && text.constructor && text.constructor.name == 'Promise') text = await text; 134 | if (typeof text !== 'string') text = inspect(text, { depth: 1 }); 135 | text = text.replace(/`/g, '`' + String.fromCharCode(8203)) 136 | .replace(/@/g, '@' + String.fromCharCode(8203)) 137 | .replace(this.token, 'yes'); 138 | return text; 139 | } 140 | } 141 | 142 | module.exports = { 143 | Client: AppBotClient 144 | } --------------------------------------------------------------------------------