From 8f7fb6dc84e6693e27ab81788cdefd9b528e9e85 Mon Sep 17 00:00:00 2001 From: Ibrahim Ansari Date: Tue, 9 Oct 2018 20:58:20 +0530 Subject: [PATCH] Move lots of logic into the Command class and modify command struct. Reduce reduncies and remove currying significantly. --- server/bot/client.ts | 103 +++++++++++++---------------- server/bot/commands/admin.ts | 4 +- server/bot/commands/admin/ban.ts | 4 +- server/bot/commands/admin/mute.ts | 4 +- server/bot/commands/admin/roles.ts | 4 +- server/bot/commands/admin/warn.ts | 14 ++-- server/bot/commands/api.ts | 18 ++--- server/bot/commands/games.ts | 14 ++-- server/bot/commands/gunfight.ts | 24 +++---- server/bot/commands/help.ts | 11 ++- server/bot/commands/tools.ts | 12 ++-- server/bot/commands/utilities.ts | 48 +++++++------- server/bot/imports/types.ts | 55 +++++++-------- 13 files changed, 151 insertions(+), 164 deletions(-) diff --git a/server/bot/client.ts b/server/bot/client.ts index 3b32947..497db1f 100644 --- a/server/bot/client.ts +++ b/server/bot/client.ts @@ -1,5 +1,5 @@ -import { Message, MessageContent, CommandGeneratorFunction, Client } from 'eris' -import { DB, Command as IveBotCommand, IveBotCommandGenerator } from './imports/types' +import { Message, MessageContent, Client } from 'eris' +import { DB, Command as IveBotCommand, IveBotCommandGenerator, Context } from './imports/types' import { Db } from 'mongodb' import { getInsult } from './imports/tools' import botCallback from '.' @@ -27,12 +27,8 @@ export class Command { /* eslint-disable no-undef */ name: string aliases: string[] - generator: ( - client: Client, db?: DB, mongoDB?: Db, commandParser?: CommandParser - ) => IveBotCommandGenerator - postGenerator?: (client: Client, db?: DB, mongoDB?: Db) => ( - message: Message, args: string[], sent?: Message - ) => void + generator: IveBotCommandGenerator + postGenerator?: (message: Message, args: string[], sent?: Message, ctx?: Context) => void argsRequired: boolean caseInsensitive: boolean deleteCommand: boolean @@ -80,85 +76,76 @@ export class Command { // No cooldown implementation. // No reaction implementation. } -} -export default class CommandParser { - commands: { [name: string]: Command } // eslint-disable-line no-undef - client: Client // eslint-disable-line no-undef - tempDB: DB // eslint-disable-line no-undef - db: Db // eslint-disable-line no-undef - constructor (client: Client, tempDB: DB, db: Db) { - this.commands = {} - this.client = client - this.tempDB = tempDB - this.db = db - this.onMessage = this.onMessage.bind(this) - } - - registerCommand = (command: IveBotCommand) => { // eslint-disable-line no-undef - this.commands[command.name] = new Command(command) - } - - requirementsCheck (command: Command, message: Message) { - if (!command.requirements) return true + requirementsCheck (message: Message) { + if (!this.requirements) return true // No role name or ID impl. - const userIDs = command.requirements.userIDs // If it doesn't exist it's a pass. - ? command.requirements.userIDs.includes(message.author.id) + const userIDs = this.requirements.userIDs // If it doesn't exist it's a pass. + ? this.requirements.userIDs.includes(message.author.id) : false // Next line calls custom if it exists. - const custom = command.requirements.custom ? command.requirements.custom(message) : false + const custom = this.requirements.custom ? this.requirements.custom(message) : false // If it's not a guild there are no permissions. if (message.channel.type !== 0) return userIDs || custom - const permissions = command.requirements.permissions + const permissions = this.requirements.permissions ? isEquivalent(Object.assign( // Assign the required permissions onto the member's permission. - message.member.permission.json, command.requirements.permissions + message.member.permission.json, this.requirements.permissions ), message.member.permission.json) // This should eval true if user has permissions. : false // If any of these are true, it's a go. return userIDs || custom || permissions } - async fixCommand (session: { // eslint-disable-next-line indent - generator: IveBotCommandGenerator, // eslint-disable-next-line indent - postGenerator?: (message: Message, args: string[], sent?: Message) => void - }, message: Message, args: string[]) { // eslint-disable-line indent + async execute (context: Context, message: Message, args: string[]) { // eslint-disable-line indent + if (!this.requirementsCheck(message)) { + message.channel.createMessage( + `**Thankfully, you don't have enough permissions for that, you ${getInsult()}.**` + ) + return + } // Define 2 vars. - let messageToSend: MessageContent|void|Promise|Promise - let toProcess: MessageContent|void|Promise|Promise|MessageContent[] - |CommandGeneratorFunction + let messageToSend: MessageContent | void | Promise | Promise // If it's a function, we call it first. - if (typeof session.generator === 'function') toProcess = session.generator(message, args) - else toProcess = session.generator - // If it's an array, we need a random response. - if (toProcess instanceof Array) messageToSend = toProcess[Math.floor(Math.random() * toProcess.length)] - else messageToSend = toProcess + if (typeof this.generator === 'function') messageToSend = this.generator(message, args, context) + else messageToSend = this.generator // We don't process Promises because we unconditionally return a Promise. // Async functions returning arrays aren't supported. return messageToSend } +} + +export default class CommandParser { + commands: { [name: string]: Command } // eslint-disable-line no-undef + client: Client // eslint-disable-line no-undef + tempDB: DB // eslint-disable-line no-undef + db: Db // eslint-disable-line no-undef + constructor (client: Client, tempDB: DB, db: Db) { + this.commands = {} + this.client = client + this.tempDB = tempDB + this.db = db + this.onMessage = this.onMessage.bind(this) + } + + registerCommand = (command: IveBotCommand) => { // eslint-disable-line no-undef + this.commands[command.name] = new Command(command) + } async executeCommand (command: Command, message: Message) { // We give our generators what they need. - const session = { - generator: command.generator(this.client, this.tempDB, this.db, this), - postGenerator: command.postGenerator - ? command.postGenerator(this.client, this.tempDB, this.db) : undefined + const context: Context = { + tempDB: this.tempDB, db: this.db, commandParser: this, client: this.client } const args = message.content.split(' ') args.shift() - // We check for requirements and arguments. - if (!this.requirementsCheck(command, message)) { - message.channel.createMessage( - `**Thankfully, you don't have enough permissions for that, you ${getInsult()}.**` - ) - return - } else if (args.length === 0 && command.argsRequired) { + // We check for arguments. + if (args.length === 0 && command.argsRequired) { message.channel.createMessage(command.invalidUsageMessage) return // Guild and DM only. } else if (command.guildOnly && message.channel.type !== 0) return else if (command.dmOnly && message.channel.type !== 1) return // We get the exact content to send. - const messageToSend = await this.fixCommand(session, message, args) + const messageToSend = await command.execute(context, message, args) // We define a sent variable to keep track. let sent if ( // No permission protection is here as well. @@ -166,7 +153,7 @@ export default class CommandParser { message.member.guild.channels.find(i => i.id === message.channel.id) .permissionsOf(this.client.user.id).has('sendMessages') ) sent = await message.channel.createMessage(messageToSend) - if (session.postGenerator) session.postGenerator(message, args, sent) + if (command.postGenerator) command.postGenerator(message, args, sent) if (command.deleteCommand) message.delete('Automatically deleted by IveBot.') } diff --git a/server/bot/commands/admin.ts b/server/bot/commands/admin.ts index fc7543c..22357c3 100644 --- a/server/bot/commands/admin.ts +++ b/server/bot/commands/admin.ts @@ -24,7 +24,7 @@ export const handlePurge: Command = { ) } }, - generator: (client) => async (message, args) => { + generator: async (message, args, { client }) => { // Check if usage is correct. if ( isNaN(+args[0]) || args.length !== 1 || +args[0] <= 0 || +args[0] > 100 @@ -52,7 +52,7 @@ export const handleKick: Command = { example: '/kick voldemort you is suck', requirements: { permissions: { 'kickMembers': true } } }, - generator: (client) => async (message, args) => { + generator: async (message, args, { client }) => { // Find the user ID. let user = getUser(message, args.shift()) if (!user) return `Specify a valid member of this guild, ${getInsult()}.` diff --git a/server/bot/commands/admin/ban.ts b/server/bot/commands/admin/ban.ts index f8e0804..1d9fdca 100644 --- a/server/bot/commands/admin/ban.ts +++ b/server/bot/commands/admin/ban.ts @@ -14,7 +14,7 @@ export const handleBan: Command = { requirements: { permissions: { 'banMembers': true } } }, aliases: ['banana', 'nuke'], - generator: (client) => async (message, args) => { + generator: async (message, args, { client }) => { // Find the user ID. const userSpecified = args.shift() let user: User = getUser(message, userSpecified) @@ -65,7 +65,7 @@ export const handleUnban: Command = { guildOnly: true, requirements: { permissions: { 'banMembers': true } } }, - generator: (client) => async (message, args) => { + generator: async (message, args, { client }) => { // Find the user ID. const userSpecified = args.shift() let user: User diff --git a/server/bot/commands/admin/mute.ts b/server/bot/commands/admin/mute.ts index ac8e761..23429eb 100644 --- a/server/bot/commands/admin/mute.ts +++ b/server/bot/commands/admin/mute.ts @@ -14,7 +14,7 @@ export const handleMute: Command = { guildOnly: true, requirements: { permissions: { 'manageMessages': true } } }, - generator: (client) => async (message, args) => { + generator: async (message, args, { client }) => { // Find the user ID. let user = getUser(message, args.shift()) if (!user) return `Specify a valid member of this guild, ${getInsult()}.` @@ -140,7 +140,7 @@ export const handleUnmute: Command = { example: '/unmute voldemort wrong person', requirements: { permissions: { 'manageMessages': true } } }, - generator: (client) => (message, args) => { + generator: (message, args, { client }) => { // Find the user ID. let user = getUser(message, args.shift()) if (!user) return `Specify a valid member of this guild, ${getInsult()}.` diff --git a/server/bot/commands/admin/roles.ts b/server/bot/commands/admin/roles.ts index 321d4be..2ad8e30 100644 --- a/server/bot/commands/admin/roles.ts +++ b/server/bot/commands/admin/roles.ts @@ -12,7 +12,7 @@ export const handleGiverole: Command = { example: '/giverole @voldemort#6931 Helper', guildOnly: true }, - generator: (client, tempDB, db) => async (message, args) => { + generator: async (message, args, { db, client }) => { // Check user for permissions. if ( !message.member.permission.has('manageRoles') || @@ -62,7 +62,7 @@ export const handleTakerole: Command = { example: '/takerole @voldemort#6931 Helper', guildOnly: true }, - generator: (client, tempDB, db) => async (message, args) => { + generator: async (message, args, { db, client }) => { // Check user for permissions. if ( !message.member.permission.has('manageRoles') || diff --git a/server/bot/commands/admin/warn.ts b/server/bot/commands/admin/warn.ts index 2731ee4..fa7a318 100644 --- a/server/bot/commands/admin/warn.ts +++ b/server/bot/commands/admin/warn.ts @@ -14,7 +14,7 @@ export const handleWarn: Command = { guildOnly: true, requirements: { permissions: { 'manageMessages': true } } }, - generator: ({ createMessage, getDMChannel }, tempDB, db) => async (message, args) => { + generator: async (message, args, { client, db }) => { // If improper arguments were provided, then we must inform the user. if (args.length < 2) return 'Correct usage: /warn ' // Now find the user ID. @@ -36,12 +36,12 @@ export const handleWarn: Command = { serverID: message.member.guild.id, date: new Date().toUTCString() }) - createMessage( - (await getDMChannel(user.id)).id, + client.createMessage( + (await client.getDMChannel(user.id)).id, `You have been warned in ${message.member.guild.name} for: ${args.join(' ')}.` ) if (message.member.guild.id === '402423671551164416') { - createMessage('402435742925848578', { + client.createMessage('402435742925848578', { content: `**${user.username}#${user.discriminator}** has been warned:`, embed: { color: 0x00AE86, @@ -74,7 +74,7 @@ export const handleWarnings: Command = { ) } }, - generator: (client, tempDB, db) => async (message, args) => { + generator: async (message, args, { client, db }) => { // If improper arguments were provided, then we must inform the user. if (args.length > 1) return 'Correct usage: /warnings (user by ID/username/mention)' // Now find the user ID. @@ -122,7 +122,7 @@ export const handleClearwarns: Command = { example: '/clearwarns voldemort', requirements: { permissions: { 'manageMessages': true } } }, - generator: ({ createMessage, getDMChannel }, tempDB, db) => async (message, args) => { + generator: async (message, args, { db }) => { // If improper arguments were provided, then we must inform the user. if (args.length !== 1) return 'Correct usage: /clearwarns ' // Now find the user ID. @@ -157,7 +157,7 @@ export const handleRemovewarn: Command = { example: '/removewarn voldemort 5adf7a0e825aa7005a4e7be2', requirements: { permissions: { 'manageMessages': true } } }, - generator: ({ createMessage, getDMChannel }, tempDB, db) => async (message, args) => { + generator: async (message, args, { db }) => { // If improper arguments were provided, then we must inform the user. if (args.length !== 2) return 'Correct usage: /removewarn ' // Now find the user ID. diff --git a/server/bot/commands/api.ts b/server/bot/commands/api.ts index 222fdff..41087aa 100644 --- a/server/bot/commands/api.ts +++ b/server/bot/commands/api.ts @@ -17,7 +17,7 @@ export const handleCat: Command = { example: '/cat', argsRequired: false }, - generator: () => async () => { + generator: async () => { try { // Fetch a cat and process it (this sounds funny to me idk why) const { file } = await (await fetch(`http://aws.random.cat/meow`)).json() @@ -38,7 +38,7 @@ export const handleRobohash: Command = { usage: '/robohash ', example: '/robohash cat voldemort#6931' }, - generator: () => (message, args) => { + generator: (message, args) => { // Get text to hash. const target = args.shift() const text = args.join('%20') @@ -63,7 +63,7 @@ export const handleApod: Command = { example: '/astronomy-picture-of-the-day 2nd March 2017', argsRequired: false }, - generator: () => async (message, args) => { + generator: async (message, args) => { // Check for date. const date = moment(args.join(' '), [ moment.ISO_8601, moment.RFC_2822, 'Do M YYYY', 'Do MM YYYY', 'Do MMM YYYY', @@ -106,7 +106,7 @@ export const handleDog: Command = { example: '/dog labrador', argsRequired: false }, - generator: () => async (message, args) => { + generator: async (message, args) => { if (args.length) { // Fetch a picture. try { @@ -134,7 +134,7 @@ export const handleUrban: Command = { example: '/urban nub', argsRequired: false // this is fun. }, - generator: () => async (message, args) => { + generator: async (message, args) => { try { // Fetch the definition and parse it to JSON. const { list } = await (await fetch( @@ -176,7 +176,7 @@ export const handleNamemc: Command = { usage: '/namemc ', example: '/namemc voldemort' }, - generator: () => async (message, args) => { + generator: async (message, args) => { if (args.length > 1) return 'Minecraft users cannot have spaces in their name.' try { // Fetch the UUID and name of the user and parse it to JSON. @@ -220,7 +220,7 @@ export const handleCurrency: Command = { usage: '/currency (amount, default: 1)', example: '/currency EUR USD 40' }, - generator: () => async (message, args) => { + generator: async (message, args) => { // For /currency list if (args.length === 1 && args[0].toLowerCase() === 'list') { return '**List of symbols:**\n' + Object.keys(currency.rates).toString().split(',').join(', ') @@ -279,7 +279,7 @@ export const handleWeather: Command = { usage: '/weather (country code) (--fahrenheit or -f)', example: '/weather Shanghai CN' }, - generator: () => async (message, args) => { + generator: async (message, args) => { const farhenheit = args.includes('--fahrenheit') || args.includes('-f') if (farhenheit) args.splice(args.includes('-f') ? args.indexOf('-f') : args.indexOf('--fahrenheit'), 1) // Get the response from our API. @@ -351,7 +351,7 @@ export const handleDefine: Command = { usage: '/define ', example: '/define cyclone' }, - generator: () => async (message, args) => { + generator: async (message, args) => { // Setup request to find word. const headers = { 'app_id': oxfordAPI.appId, 'app_key': oxfordAPI.appKey, Accept: 'application/json' } // Search for the word, destructure for results, and then pass them on to our second request. diff --git a/server/bot/commands/games.ts b/server/bot/commands/games.ts index b963e9a..8480216 100644 --- a/server/bot/commands/games.ts +++ b/server/bot/commands/games.ts @@ -32,7 +32,7 @@ export const handleChoose: Command = { example: '/choose cake|ice cream|pasta', usage: '/choose