Skip to content

Commit

Permalink
Add slashOptions to commands, port some commands.
Browse files Browse the repository at this point in the history
Current approach is to have slashGenerator and generator.
  • Loading branch information
retrixe committed Aug 23, 2021
1 parent 8001ba1 commit c65e505
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const handleCat: Command = {
example: '/cat',
argsRequired: false
},
slashGenerator: true,
generator: async () => {
try {
// Fetch a cat and process it (this sounds funny to me idk why)
Expand Down
140 changes: 131 additions & 9 deletions src/commands/games.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommandOptionType } from 'slash-create'
import { Command } from '../imports/types.js'
import { evaluate } from 'mathjs'

Expand Down Expand Up @@ -29,7 +30,17 @@ export const handleChoose: Command = {
description: 'Choose between multiple options.',
fullDescription: 'Choose between multiple options.',
example: '/choose cake|ice cream|pasta',
usage: '/choose <option 1>|(option 2)|(option 3)...'
usage: '/choose <option 1>|(option 2)|(option 3)...',
slashOptions: [{
name: 'choices',
description: 'The choices to choose from. Each option should be separated like: item1|item2',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => {
const choices = context.options.choices.split('|')
return `I choose: ${choices[Math.floor(Math.random() * choices.length)]}`
},
generator: (message, args) => {
// Is it used correctly?
Expand All @@ -46,8 +57,15 @@ export const handleReverse: Command = {
description: 'Reverse a sentence.',
fullDescription: 'Reverse a sentence.',
example: '/reverse hello',
usage: '/reverse <text>'
usage: '/reverse <text>',
slashOptions: [{
name: 'text',
description: 'The text to reverse.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => context.options.text.split('').reverse().join(''),
generator: (message, args) => args.join(' ').split('').reverse().join('')
}

Expand All @@ -58,8 +76,15 @@ export const handle8ball: Command = {
fullDescription: 'Random answers to random questions.',
usage: '/8ball <question>',
example: '/8ball Will I flunk my exam?',
invalidUsageMessage: 'Please ask the 8ball a question.'
invalidUsageMessage: 'Please ask the 8ball a question.',
slashOptions: [{
name: 'question',
description: 'The question you wish to ask the 8ball.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: true,
generator: () => {
// Possible responses, taken from Diary Of A Wimpy Kid: Hard Luck.
const responses = [
Expand All @@ -81,7 +106,23 @@ export const handleZalgo: Command = {
description: 'The zalgo demon\'s writing.',
fullDescription: 'The zalgo demon\'s handwriting.',
usage: '/zalgo <text>',
example: '/zalgo sup'
example: '/zalgo sup',
slashOptions: [{
name: 'text',
description: 'The text to convert into the zalgo demon\'s handwriting.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => {
let newMessage = ''
context.options.text.split('').forEach((element: string) => {
newMessage += element
for (let i = 0; i < Math.floor(Math.random() * 5) + 1; i++) {
newMessage += characters[Math.floor(Math.random() * characters.length)]
}
})
return newMessage.length >= 2000 ? context.options.text : newMessage
},
generator: (message, args) => {
const textToZalgo = args.join(' ').split('')
Expand All @@ -103,7 +144,20 @@ export const handleDezalgo: Command = {
description: 'The zalgo demon\'s writing.',
fullDescription: 'Read the zalgo demon\'s writing.',
usage: '/dezalgo <text>',
example: '/dezalgo ḥ̛̓e̖l̽͞҉lͦͅoͥ'
example: '/dezalgo ḥ̛̓e̖l̽͞҉lͦͅoͥ',
slashOptions: [{
name: 'text',
description: 'The zalgo demon\'s handwriting to be converted to regular text.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => {
let newMessage = ''
context.options.text.split('').forEach((element: string) => {
if (!characters.includes(element)) newMessage += element
})
return newMessage
},
generator: (message, args) => {
let newMessage = ''
Expand All @@ -121,7 +175,30 @@ export const handleRepeat: Command = {
description: 'Repeat a string.',
fullDescription: 'Repeat a string.',
usage: '/repeat <number of times> <string to repeat>',
example: '/repeat 10 a'
example: '/repeat 10 a',
slashOptions: [{
name: 'number',
description: 'The number of times to repeat the text.',
required: true,
type: CommandOptionType.INTEGER
}, {
name: 'text',
description: 'The text to repeat as many times as you want.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => {
const number = context.options.number
const text = context.options.text as string
if (number * text.length >= 2001) {
return { content: 'To prevent spam, your excessive message has not been repeated.', error: true }
} else if (text === '_' || text === '*' || text === '~') {
return { content: 'This is known to lag users and is disabled.', error: true }
}
let generatedMessage = ''
for (let x = 0; x < number; x++) { generatedMessage += text }
return generatedMessage
},
generator: (message, args) => {
// All arguments.
Expand All @@ -147,7 +224,29 @@ export const handleRandom: Command = {
fullDescription: 'Returns a random number, by default between 0 and 10.',
usage: '/random (starting number) (ending number)',
example: '/random 1 69',
argsRequired: false
argsRequired: false,
slashOptions: [{
name: 'start',
description: 'The number which the random number should be higher than or equal to.',
required: false,
type: CommandOptionType.INTEGER
}, {
name: 'end',
description: 'The number which the random number should be lower than.',
required: false,
type: CommandOptionType.INTEGER
}]
},
slashGenerator: context => {
if (typeof context.options.start === 'number' && typeof context.options.end === 'number') {
const number1 = context.options.start
const number2 = context.options.end
return `The number.. is.. ${Math.floor(Math.random() * (number2 - number1)) + number1}`
} else if (typeof context.options.end === 'number') {
return `The number.. is.. ${Math.floor(Math.random() * context.options.end)}`
} else if (typeof context.options.start === 'number') {
return { content: 'You must provide an end number if providing a start number.', error: true }
} else return `The number.. is.. ${Math.floor(Math.random() * 10)}`
},
generator: (message, args) => {
// If argument length is 1 and the argument is a number..
Expand Down Expand Up @@ -175,7 +274,21 @@ export const handleCalculate: Command = {
More info here: https://mathjs.org/docs/expressions/syntax.html`,
usage: '/calculate <expression>',
example: '/calculate 2 + 2',
invalidUsageMessage: 'Specify an expression >_<'
invalidUsageMessage: 'Specify an expression >_<',
slashOptions: [{
name: 'expression',
description: 'The math expression to be evaluated.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => {
try {
const expr = context.options.expression
return `${evaluate(expr.split(',').join('.').split('÷').join('/').toLowerCase())}`
} catch (e) {
return { content: 'Invalid expression >_<', error: true }
}
},
generator: (message, args) => {
try {
Expand All @@ -192,8 +305,17 @@ export const handleDistort: Command = {
description: 'Pretty distorted text.',
fullDescription: 'Pretty distorted text.',
usage: '/distort <text>',
example: '/distort lol'
example: '/distort lol',
slashOptions: [{
name: 'text',
description: 'The text to be distorted.',
required: true,
type: CommandOptionType.STRING
}]
},
slashGenerator: context => context.options.text.split(' ').map((i: string) => (
i.split('').join('*') + (i.length % 2 === 0 ? '*' : '')
)).join(' '),
generator: (message, args) => args.map(i => (
i.split('').join('*') + (i.length % 2 === 0 ? '*' : '')
)).join(' ')
Expand Down
1 change: 1 addition & 0 deletions src/commands/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const handleUptime: Command = {
example: '/uptime',
argsRequired: false
},
slashGenerator: true,
generator: () => {
const d = moment.duration(Math.floor(process.uptime() * 1000))
const days = Math.floor(d.asDays())
Expand Down
7 changes: 6 additions & 1 deletion src/imports/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AdvancedMessageContent, Client, Message } from 'eris'
import CommandParser from '../client.js'
import { Db } from 'mongodb'
import { TriviaSession } from '../commands/trivia.js'
import { ApplicationCommandOption, CommandContext } from 'slash-create'

export interface DB {
gunfight: {
Expand Down Expand Up @@ -33,13 +34,16 @@ export interface Context { tempDB: DB, db: Db, commandParser: CommandParser, cli
export type CommandResponse = string | AdvancedMessageContent & { error?: boolean }
export type IveBotCommandGeneratorFunction = (msg: Message, args: string[], ctx: Context) =>
void | Promise<void> | CommandResponse | Promise<CommandResponse>
export type IveBotSlashGeneratorFunction = (context: CommandContext, ctx: Context) =>
void | Promise<void> | CommandResponse | Promise<CommandResponse>
export type IveBotCommandGenerator = IveBotCommandGeneratorFunction|string|AdvancedMessageContent
export interface Command {
opts: CommandOptions
aliases?: string[]
name: string
generator: IveBotCommandGenerator
postGenerator?: (message: Message, args: string[], sent?: Message, ctx?: Context) => void
slashGenerator?: true | IveBotSlashGeneratorFunction
}
export interface CommandOptions {
argsRequired?: boolean
Expand All @@ -58,7 +62,8 @@ export interface CommandOptions {
userIDs?: string[]
roleNames?: string[]
custom?: (message: Message) => boolean
permissions?: {}
permissions?: { [permission: string]: boolean }
roleIDs?: string[]
}
slashOptions?: ApplicationCommandOption[]
}
53 changes: 40 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'json5/lib/require.js'
// Tokens and stuff.
import { AdvancedMessageContent, Client, MessageWebhookContent } from 'eris'
import { SlashCommand, SlashCreator, GatewayServer } from 'slash-create'
import { Client, MessageWebhookContent } from 'eris'
import { SlashCommand, SlashCreator, GatewayServer, CommandContext } from 'slash-create'
// Get MongoDB.
import { MongoClient } from 'mongodb'
// Import fs.
Expand All @@ -10,7 +10,7 @@ import { inspect } from 'util'
import http from 'http'
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto'
// Import types.
import { DB, Command } from './imports/types.js'
import { DB, Command, IveBotSlashGeneratorFunction } from './imports/types.js'
// Import the bot.
import CommandParser from './client.js'
import { guildMemberAdd, guildMemberRemove, guildDelete, guildBanAdd } from './events.js'
Expand Down Expand Up @@ -52,14 +52,38 @@ creator.withServer(new GatewayServer(
const commandToSlashCommand = (command: Command): SlashCommand => {
class IveBotSlashCommand extends SlashCommand {
constructor (creator: SlashCreator) {
super(creator, { name: command.name, description: command.opts.description, options: [] })
const reqs = command.opts.requirements
const defaultPermission = !(reqs && (reqs.userIDs || reqs.custom))
const requiredPermissions = []
if (reqs && reqs.permissions) {
// TODO: Does not support falsy permissions. Do we convert this to straightforward [] type?
for (const permission in reqs.permissions) {
if (reqs.permissions[permission]) {
const translatedName = permission.toLowerCase().split('_').map((value, index) => (
index === 0 ? value : value.substr(0, 1).toUpperCase() + value.substr(1)
))
requiredPermissions.push(translatedName.join(''))
}
}
}
super(creator, {
name: command.name,
description: command.opts.description.replace(/</g, '').replace(/>/g, ''),
defaultPermission,
requiredPermissions,
options: command.opts.slashOptions || []
})
}

async run (): Promise<string | MessageWebhookContent | void> {
let response: string | AdvancedMessageContent | void
async run (ctx: CommandContext): Promise<string | MessageWebhookContent | void> {
if (typeof command.generator !== 'function') return command.generator
else response = await Promise.resolve(command.generator(undefined, [], { client, db, tempDB, commandParser }))
return typeof response === 'object' ? { ...response, embeds: [response.embed] } : response
const func: IveBotSlashGeneratorFunction = command.slashGenerator === true
? command.generator as any
: command.slashGenerator
const response = await Promise.resolve(func(ctx, { client, db, tempDB, commandParser }))
return typeof response === 'object' && response.embed
? { ...response, embeds: [response.embed] }
: response
}
}
return new IveBotSlashCommand(creator)
Expand All @@ -79,7 +103,7 @@ console.log('Bot connected successfully to MongoDB.')
const db = mongoClient.db('ivebot')
const bubbleWrap = <F extends (...args: any[]) => any>(func: F) =>
(...args: Parameters<F>) => { func(...args).catch(console.error) }
// When a server loses a member, it will callback.
// When a server loses a member, it will callback.
client.on('guildMemberAdd', bubbleWrap(guildMemberAdd(client, db, tempDB)))
client.on('guildMemberRemove', bubbleWrap(guildMemberRemove(client, db)))
client.on('guildBanAdd', bubbleWrap(guildBanAdd(client, db)))
Expand Down Expand Up @@ -107,13 +131,16 @@ for (const commandFile of commandFiles) {
if (commandName === 'TriviaSession') return
const command = commands[commandName]
commandParser.registerCommand(command)
if (typeof command.generator !== 'function') creator.registerCommand(commandToSlashCommand(command))
else if (command.name === 'uptime') creator.registerCommand(commandToSlashCommand(command))
else if (command.name === 'cat') creator.registerCommand(commandToSlashCommand(command))
// TODO: Custom and user ID requirements not handled yet.
const reqs = command.opts.requirements
const hasCustomReqs = (reqs && (reqs.custom || reqs.userIDs))
if (!hasCustomReqs && (typeof command.generator !== 'function' || command.slashGenerator)) {
creator.registerCommand(commandToSlashCommand(command))
}
})
}
}
creator.syncCommands({ deleteCommands: true })
creator.syncCommands()

// Register setInterval to fulfill delayed tasks.
setInterval(() => {
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2478,9 +2478,9 @@ path-key@^3.1.0:
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==

path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==

path-type@^3.0.0:
version "3.0.0"
Expand Down

0 comments on commit c65e505

Please sign in to comment.