Skip to content

Commit

Permalink
Move lots of logic into the Command class and modify command struct.
Browse files Browse the repository at this point in the history
Reduce reduncies and remove currying significantly.
  • Loading branch information
retrixe committed Oct 9, 2018
1 parent 17e2bf2 commit 8f7fb6d
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 164 deletions.
103 changes: 45 additions & 58 deletions server/bot/client.ts
Original file line number Diff line number Diff line change
@@ -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 '.'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -80,93 +76,84 @@ 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<MessageContent>|Promise<void>
let toProcess: MessageContent|void|Promise<MessageContent>|Promise<void>|MessageContent[]
|CommandGeneratorFunction
let messageToSend: MessageContent | void | Promise<MessageContent> | Promise<void>
// 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.
messageToSend && message.member &&
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.')
}

Expand Down
4 changes: 2 additions & 2 deletions server/bot/commands/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()}.`
Expand Down
4 changes: 2 additions & 2 deletions server/bot/commands/admin/ban.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions server/bot/commands/admin/mute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()}.`
Expand Down Expand Up @@ -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()}.`
Expand Down
4 changes: 2 additions & 2 deletions server/bot/commands/admin/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') ||
Expand Down Expand Up @@ -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') ||
Expand Down
14 changes: 7 additions & 7 deletions server/bot/commands/admin/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <user> <reason>'
// Now find the user ID.
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <user>'
// Now find the user ID.
Expand Down Expand Up @@ -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 <user> <warning ID>'
// Now find the user ID.
Expand Down
18 changes: 9 additions & 9 deletions server/bot/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -38,7 +38,7 @@ export const handleRobohash: Command = {
usage: '/robohash <cat/robot/monster/head> <text to hash>',
example: '/robohash cat voldemort#6931'
},
generator: () => (message, args) => {
generator: (message, args) => {
// Get text to hash.
const target = args.shift()
const text = args.join('%20')
Expand All @@ -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',
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -176,7 +176,7 @@ export const handleNamemc: Command = {
usage: '/namemc <premium Minecraft username>',
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.
Expand Down Expand Up @@ -220,7 +220,7 @@ export const handleCurrency: Command = {
usage: '/currency <currency symbol to convert from> <currency symbol to convert to> (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(', ')
Expand Down Expand Up @@ -279,7 +279,7 @@ export const handleWeather: Command = {
usage: '/weather <city name> (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.
Expand Down Expand Up @@ -351,7 +351,7 @@ export const handleDefine: Command = {
usage: '/define <term>',
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.
Expand Down
Loading

0 comments on commit 8f7fb6d

Please sign in to comment.