diff --git a/discord/commands/Eval.ts b/discord/commands/Eval.ts new file mode 100644 index 0000000..bc2384f --- /dev/null +++ b/discord/commands/Eval.ts @@ -0,0 +1,75 @@ +import DiscordInteractionCommand from "../../util/DiscordInteractionCommand"; +import { ChatInputCommandInteraction } from "discord.js"; +import { inspect } from "util"; +import { discordBotToken } from "../../config.json"; + +export default class Eval extends DiscordInteractionCommand { + // This is a list of IDs that are allowed to use this command. + private listOfAllowedIDs: string[]; + + constructor() { + super("eval", "Executes arbitrary JS code and returns the output."); + // this option is required and is a string of JavaScript code to execute + this.builder.addStringOption(option => option.setName("code").setDescription("The code to execute.").setRequired(true)); + // this option is optional and is a boolean that determines whether the code should be run as an async function + this.builder.addBooleanOption(option => option.setName("async").setDescription("Whether to run the code as an async function.").setRequired(false)); + // this option is optional and is an integer that determines the depth of the eval inspection + this.builder.addIntegerOption(option => option.setName("depth").setDescription("The depth of the inspection.").setRequired(false)); + + this.listOfAllowedIDs = [ + "278620217221971968", // Matthew + ]; + } + + public async execute(interaction: ChatInputCommandInteraction) { + // @ts-ignore + let evalString = interaction.options.getString("code").trim(); + if (evalString == null) return interaction.reply({ content: "You must provide code to evaluate.", ephemeral: true }); + if (!this.listOfAllowedIDs.includes(interaction.user.id)) return interaction.reply({ content: "Permission denied.", ephemeral: true }); + await interaction.deferReply({ephemeral: true}); + + // set scoped variables to be able to access over eval + const guild = interaction.guild || interaction.client.guilds.cache.get(this.GUILD_ID); + // the output of eval() is stored in evaled + let evaled: any; + let depth: number | null = 0; + // if depth option exists, set the depth variable to the value provided by the user + if (interaction.options.getInteger("depth") != null) { + depth = interaction.options.getInteger("depth"); + } + + // if command specified as async, swap the evalString in an async function + if (interaction.options.getBoolean("async")) { + evalString = `(async () => { ${evalString} })()`; + } + + try { + // eslint-disable-next-line no-eval + evaled = await eval(evalString); + if (typeof evaled !== 'string') { + evaled = inspect(evaled, { depth }); + } + if (evaled === undefined) { + evaled = 'undefined'; + } + } catch (error) { + // @ts-ignore + evaled = error.stack; + } + + // replaces all instances of the bot token with [REDACTED] in output + evaled = evaled.replace(new RegExp(discordBotToken, 'gi'), '[REDACTED]'); + + // TODO: Figure this out. + /*const display = this.client.util.splitString(evaled, 1975); + if (display[5]) { + try { + const { data } = await axios.post('https://snippets.cloud.libraryofcode.org/documents', display.join('')); + return this.success(ctx.message.channel, `Your evaluation evaled can be found on https://snippets.cloud.libraryofcode.org/${data.key}`); + } catch (error) { + return this.error(ctx.message.channel, `${error}`); + } + }*/ + await interaction.editReply({content: `\`\`\`js\n${evaled}\n\`\`\``}) + } +}