Last active
February 14, 2021 23:55
-
-
Save Benricheson101/db14f1b258428d2f4e112bc9b00b4b56 to your computer and use it in GitHub Desktop.
Overcomplicated Discord.js eval command
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { runInNewContext } = require("vm"); | |
const Discord = require("discord.js"); | |
const chalk = require("chalk"); | |
const { inspect } = require("util"); | |
const fetch = require("node-fetch"); | |
const { colors: { default: defaultColor } } = require("../../config.json"); | |
const options = { | |
callback: false, | |
stdout: true, | |
stderr: true | |
}; | |
if (!args[0]) return await message.channel.send(":x: You must provide code to execute!"); | |
const script = parseCodeblock(args.join(" ")); | |
if (!( | |
await confirmation( | |
message, | |
new Discord.MessageEmbed() | |
.setTitle(":warning: Are you sure you would like to execute the following code:") | |
.setDescription("```js\n" + script + "```") | |
.setColor(defaultColor), | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return; | |
const context = { | |
client, | |
message, | |
args, | |
Discord, | |
console, | |
require, | |
process, | |
global | |
}; | |
const scriptOptions = { | |
filename: `${message.author.id}@${message.guild.id}`, | |
timeout: 60000, | |
displayErrors: true | |
}; | |
let start = Date.now(); | |
let result = execute(`"use strict"; (async () => { ${script} })()`, context, scriptOptions); | |
let end = Date.now(); | |
if (((await result) && !(await result).stdout) && ((await result) && !(await result).callbackOutput) && ((await result) && !(await result).stderr)) { | |
if (!( | |
await confirmation( | |
message, | |
":warning: Nothing was returned. Would you like to run the code again with implicit return?", | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return; | |
else { | |
start = Date.now(); | |
result = execute(`"use strict"; (async () => ${script} )()`, context, scriptOptions); | |
end = Date.now(); | |
} | |
} | |
result | |
.then(async (res) => { | |
if ( | |
(options.stdout && res && res.stdout) || | |
(options.stderr && res && res.stderr) || | |
(options.callback && res && res.callbackOutput) | |
) { | |
console.log(chalk`{red {strikethrough -}[ {bold Eval Output} ]{strikethrough ---------}}`); | |
if (options.callback && res.callbackOutput) console.log(res.callbackOutput); | |
if (options.stdout && res.stdout) { | |
console.log(chalk`{red {strikethrough -}[ {bold stdout} ]{strikethrough --------------}}`); | |
console.log(res.stdout); | |
} | |
if (options.stderr && res.stderr) { | |
console.log(chalk`{red {strikethrough -}[ {bold stderr} ]{strikethrough --------------}}`); | |
console.error(res.stderr); | |
} | |
console.log(chalk`{red {strikethrough -}[ {bold End} ]{strikethrough -----------------}}`); | |
} | |
if ( | |
res.callbackOutput && (typeof res.callbackOutput === "string" ? res.callbackOutput : inspect(res.callbackOutput)).includes(client.token) || | |
res.stdout && res.stdout.includes(client.token) || | |
res.stderr && res.stderr.includes(client.token) | |
) { | |
if (!( | |
await confirmation( | |
message, | |
":bangbang: The bot token is likely located somewhere in the output of your code. Would you like to display the output?", | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return; | |
} | |
const embed = await generateEmbed(script, res, { start, end }); | |
const msg = await message.channel.send({ embed: embed }); | |
if (!( | |
await confirmation( | |
message, | |
":information_source: Would you like to post the output of this command on hastebin?", | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return; | |
const evalOutput = []; | |
if (res.callbackOutput) { | |
evalOutput.push( | |
"-[ Eval Output ]---------", | |
typeof res.callbackOutput === "string" ? res.callbackOutput : inspect(res.callbackOutput) | |
); | |
} | |
if (res.stdout) { | |
evalOutput.push( | |
"-[ stdout ]--------------", | |
typeof res.stdout === "string" ? res.stdout : inspect(res.stdout) | |
); | |
} | |
if (res.stderr) { | |
evalOutput.push( | |
"-[ stderr ]--------------", | |
typeof res.stderr === "string" ? res.stderr : inspect(res.stderr) | |
); | |
} | |
const body = await fetch("https://hastebin.com/documents", { | |
method: "post", | |
body: evalOutput.join("\n") | |
}) | |
.then(async (res) => await res.json()); | |
await msg.edit({ embed: embed.addField(":notepad_spiral: Hastebin", `https://hastebin.com/${body.key}`) }); | |
}); | |
async function execute (code, context, options) { | |
return await new Promise((resolve) => { | |
try { | |
captureOutput(() => runInNewContext(code, context, options)) | |
.then(resolve) | |
.catch(resolve); | |
} catch (err) { | |
resolve(err); | |
} | |
}); | |
} | |
async function generateEmbed (code, outs, { start, end }) { | |
const output = typeof outs && outs.callbackOutput && outs.callbackOutput.then === "function" ? await outs && outs.callbackOutput : outs && outs.callbackOutput; | |
const stdout = outs && outs.stdout; | |
const stderr = outs && outs.stderr; | |
const embed = new Discord.MessageEmbed() | |
.setFooter(`Execution time: ${end - start}ms`) | |
.setTimestamp(); | |
if (output) { | |
embed | |
.setTitle(":outbox_tray: Output:") | |
.setDescription("```js\n" + ((typeof output === "string" ? output : inspect(output)) || "undefined").substring(0, 2000) + "```"); | |
} | |
if (stdout) embed.addField(":desktop: stdout", "```js\n" + ((typeof stdout === "string" ? stdout : inspect(stdout)) || "undefined").substring(0, 1000) + "```"); | |
if (stderr) embed.addField(":warning: stderr", "```js\n" + ((typeof stderr === "string" ? stderr : inspect(stderr)) || "undefined").substring(0, 1000) + "```"); | |
if (!embed.fields.length && !embed.description) embed.setTitle("Nothing was returned."); | |
if ((stdout && !isError(outs && outs.callbackOutput)) || (stdout && !output) || (!stdout && !output && !stderr)) embed.setColor("GREEN"); | |
else if (!stdout && !output && stderr) embed.setColor("YELLOW"); | |
else embed.setColor(isError(output) ? "RED" : "GREEN"); | |
embed.addField(":inbox_tray: Input", "```js\n" + code.substring(0, 1000) + "```"); | |
return embed; | |
} | |
function isError (object) { | |
const name = object && object.constructor && object.constructor.name; | |
if (!name) return true; | |
return /.*Error$/.test(name); | |
} | |
// Code from: https://github.com/lifeguardbot/lifeguard/blob/a31f57b5164d95d16f0dd961c10a5b77dc9e7bd4/src/plugins/dev/eval.ts#L6-L13 | |
function parseCodeblock (script) { | |
const cbr = /^(([ \t]*`{3,4})([^\n]*)([\s\S]+?)(^[ \t]*\2))/gm; | |
const result = cbr.exec(script); | |
if (result) { | |
return result[4]; | |
} | |
return script; | |
} | |
/** | |
* Ask for confirmation before proceeding | |
* @param {Message} message Discord.js message object | |
* @param {string} confirmationMessage Ask for confirmation | |
* @param {ConfirmationOptions} [options] Options | |
* @param {string} [options.confirmMessage] Edit the message upon confirmation | |
* @param {string | MessageEmbed} [options.denyMessage] Edit the message upon denial | |
* @param {number} options.time Timeout | |
* @param {boolean} [options.keepReactions] Keep reactions after reacting | |
* @param {boolean} [options.deleteAfterReaction] Delete the message after reaction (takes priority over all other messages) | |
* @example | |
* const confirmationMessage: string = "Are you sure you would like to stop the bot?" | |
* const options = { | |
* confirmMessage: "Shutting down...", | |
* denyMessage: "Shutdown cancelled." | |
* } | |
* | |
* const proceed = await confirmation(message, confirmationMessage, options) | |
* | |
* if (proceed) process.exit(0) | |
*/ | |
async function confirmation (message, confirmationMessage = {}, options = {}) { | |
const yesReaction = "✔️"; | |
const noReaction = "✖️"; | |
const filter = ({ emoji: { name } }, { id }) => (name === yesReaction || name === noReaction) && id === message.author.id; | |
const msg = await message.channel.send(confirmationMessage); | |
await msg.react(yesReaction); | |
await msg.react(noReaction); | |
const e = (await msg.awaitReactions(filter, { max: 1, time: options && options.time || 300000 })).first(); | |
if (options && options.deleteAfterReaction) msg.delete(); | |
else if (!options && options.keepReactions) msg.reactions.removeAll(); | |
if (e && e.emoji && e.emoji.name === yesReaction) { | |
if (options && options.confirmMessage && !options.deleteAfterReaction) await msg.edit(options && options.confirmMessage instanceof Discord.MessageEmbed ? { embed: options && options.confirmMessage, content: null } : { embed: null, content: options && options.confirmMessage }); | |
return true; | |
} else { | |
if (options && options.denyMessage && !options.deleteAfterReaction) await msg.edit(options && options.denyMessage instanceof Discord.MessageEmbed ? { embed: options && options.denyMessage, content: null } : { embed: null, content: options && options.denyMessage }); | |
return false; | |
} | |
} | |
/** | |
* Capture stdout and stderr while executing a function | |
* @param {Function} callback The callback function to execute | |
* @returns {Promise<CapturedOutput>} stdout, stderr and callback outputs | |
*/ | |
async function captureOutput (callback) { | |
return await new Promise((resolve, reject) => { | |
const oldProcess = { ...process }; | |
let stdout = ""; | |
let stderr = ""; | |
// overwrite stdout write function | |
process.stdout.write = (str) => { | |
stdout += str; | |
return true; | |
}; | |
// overwrite stderr write function | |
process.stderr.write = (str) => { | |
stderr += str; | |
return true; | |
}; | |
try { | |
const c = callback(); | |
delete process.stdout.write; | |
process.stdout.write = oldProcess.stdout.write; | |
delete process.stderr.write; | |
process.stderr.write = oldProcess.stderr.write; | |
return c | |
.catch((c) => reject({ stdout, stderr, callbackOutput: c })) // eslint-disable-line prefer-promise-reject-errors | |
.then((callbackOutput) => resolve({ stdout, stderr, callbackOutput })); | |
} catch (error) { | |
delete process.stdout.write; | |
process.stdout.write = oldProcess.stdout.write; | |
delete process.stderr.write; | |
process.stderr.write = oldProcess.stderr.write; | |
return reject({ stdout, stderr, callbackOutput: error }); // eslint-disable-line prefer-promise-reject-errors | |
} | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ts version | |
import { runInNewContext, RunningScriptOptions, Context } from 'vm' | |
import * as Discord from 'discord.js' | |
import * as chalk from 'chalk' | |
import { findBestMatch } from 'string-similarity' | |
import { inspect } from 'util' | |
import fetch from 'node-fetch' | |
const options: any = { | |
callback: false, | |
stdout: true, | |
stderr: true | |
} | |
if (!args[0]) return await message.channel.send(':x: You must provide code to execute!') | |
const script: string = parseCodeblock(args.join(' ')) | |
if (!( | |
await confirmation( | |
message, | |
new Discord.MessageEmbed() | |
.setTitle(':warning: Are you sure you would like to execute the following code:') | |
.setDescription('```js\n' + script + '```') | |
.setColor(client.constants.colors.default), | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return | |
const context: Context = { | |
client, | |
message, | |
args, | |
Discord, | |
console, | |
require, | |
process, | |
global | |
} | |
const scriptOptions: RunningScriptOptions = { | |
filename: `${message.author.id}@${message.guild.id}`, | |
timeout: 60000, | |
displayErrors: true | |
} | |
let start: number = Date.now() | |
let result: any = execute(`'use strict'; (async () => { ${script} })()`, context, scriptOptions) | |
let end: number = Date.now() | |
if (!(await result)?.stdout && !(await result)?.callbackOutput && !(await result)?.stderr) { | |
if (!( | |
await confirmation( | |
message, | |
':warning: Nothing was returned. Would you like to run the code again with implicit return?', | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return | |
else { | |
start = Date.now() | |
result = execute(`'use strict'; (async () => ${script} )()`, context, scriptOptions) | |
end = Date.now() | |
} | |
} | |
interface Output { stdout: string, stderr: string, callbackOutput: any } | |
result | |
.then(async (res: Output) => { | |
if ( | |
(options.stdout && res?.stdout) || | |
(options.stderr && res?.stderr) || | |
(options.callback && res?.callbackOutput) | |
) { | |
console.log(chalk`{red {strikethrough -}[ {bold Eval Output} ]{strikethrough ---------}}`) | |
if (options.callback && res.callbackOutput) console.log(res.callbackOutput) | |
if (options.stdout && res.stdout) { | |
console.log(chalk`{red {strikethrough -}[ {bold stdout} ]{strikethrough --------------}}`) | |
console.log(res.stdout) | |
} | |
if (options.stderr && res.stderr) { | |
console.log(chalk`{red {strikethrough -}[ {bold stderr} ]{strikethrough --------------}}`) | |
console.error(res.stderr) | |
} | |
console.log(chalk`{red {strikethrough -}[ {bold End} ]{strikethrough -----------------}}`) | |
} | |
if ( | |
matchString(client.token, inspect(res.callbackOutput).split(' '), { minRating: 0.6 }) || | |
matchString(client.token, inspect(res.stdout).split(' '), { minRating: 0.6 }) || | |
matchString(client.token, inspect(res.stderr).split(' '), { minRating: 0.6 }) | |
) { | |
if (!( | |
await confirmation( | |
message, | |
':bangbang: The bot token is likely located somewhere in the output of your code. Would you like to display the output?', | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return | |
} | |
const embed: Discord.MessageEmbed = await generateEmbed(script, res, { start, end }) | |
const msg = await message.channel.send({ embed: embed }) | |
if (!( | |
await confirmation( | |
message, | |
':information_source: Would you like to post the output of this command on hastebin?', | |
{ | |
deleteAfterReaction: true | |
} | |
) | |
)) return | |
const evalOutput: string[] = [] | |
if (res.callbackOutput) { | |
evalOutput.push( | |
'-[ Eval Output ]---------', | |
typeof res.callbackOutput === 'string' ? res.callbackOutput : inspect(res.callbackOutput) | |
) | |
} | |
if (res.stdout) { | |
evalOutput.push( | |
'-[ stdout ]--------------', | |
typeof res.stdout === 'string' ? res.stdout : inspect(res.stdout) | |
) | |
} | |
if (res.stderr) { | |
evalOutput.push( | |
'-[ stderr ]--------------', | |
typeof res.stderr === 'string' ? res.stderr : inspect(res.stderr) | |
) | |
} | |
const body = await fetch('https://hastebin.com/documents', { | |
method: 'post', | |
body: evalOutput.join('\n') | |
}) | |
.then(async (res) => await res.json()) | |
await msg.edit({ embed: embed.addField(':notepad_spiral: Hastebin', `https://hastebin.com/${body.key as string}`) }) | |
}) | |
async function execute (code: string, context: Context, options: object): Promise<{ stdout: string, stderr: string, callbackOutput?: any }> { | |
return await new Promise((resolve) => { | |
try { | |
captureOutput(() => runInNewContext(code, context, options)) | |
.then(resolve) | |
.catch(resolve) | |
} catch (err) { | |
resolve(err) | |
} | |
}) | |
} | |
async function generateEmbed (code: string, outs: any, { start, end }: { start: number, end: number }): Promise<Discord.MessageEmbed> { | |
const output = typeof outs?.callbackOutput?.then === 'function' ? await outs?.callbackOutput : outs?.callbackOutput | |
const stdout = outs?.stdout | |
const stderr = outs?.stderr | |
const embed: Discord.MessageEmbed = new Discord.MessageEmbed() | |
.setFooter(`Execution time: ${end - start}ms`) | |
.setTimestamp() | |
if (output) { | |
embed | |
.setTitle(':outbox_tray: Output:') | |
.setDescription('```js\n' + ((typeof output === 'string' ? output : inspect(output)) || 'undefined')?.substring(0, 2000) + '```') | |
} | |
if (stdout) embed.addField(':desktop: stdout', '```js\n' + ((typeof stdout === 'string' ? stdout : inspect(stdout)) || 'undefined')?.substring(0, 1000) + '```') | |
if (stderr) embed.addField(':warning: stderr', '```js\n' + ((typeof stderr === 'string' ? stderr : inspect(stderr)) || 'undefined')?.substring(0, 1000) + '```') | |
if (!embed.fields.length && !embed.description) embed.setTitle('Nothing was returned.') | |
if ((stdout && !isError(outs?.callbackOutput)) || (stdout && !output) || (!stdout && !output && !stderr)) embed.setColor('GREEN') | |
else if (!stdout && !output && stderr) embed.setColor('YELLOW') | |
else embed.setColor(isError(output) ? 'RED' : 'GREEN') | |
embed.addField(':inbox_tray: Input', '```js\n' + code.substring(0, 1000) + '```') | |
return embed | |
} | |
function isError (object: object): boolean { | |
const name = object?.constructor?.name | |
if (!name) return true | |
return /.*Error$/.test(name) | |
} | |
// Code from: https://github.com/lifeguardbot/lifeguard/blob/a31f57b5164d95d16f0dd961c10a5b77dc9e7bd4/src/plugins/dev/eval.ts#L6-L13 | |
function parseCodeblock (script: string): string { | |
const cbr = /^(([ \t]*`{3,4})([^\n]*)([\s\S]+?)(^[ \t]*\2))/gm | |
const result = cbr.exec(script) | |
if (result) { | |
return result[4] | |
} | |
return script | |
} | |
/** | |
* Find the closest matching string from an array | |
* @param {string} search The string to compare | |
* @param {string[]} mainStrings The strings to find the closest match in | |
* @returns {string | null} | |
* @example | |
* const search: string = 'Admin' | |
* const strings: string[] = ['Administrator', 'Developer', 'Moderator'] | |
* const options: MatchStringOptions = { minRating: 0.4 } | |
* | |
* const match: string | null = matchString(search, strings, options) | |
* // match: 'Administrator' | |
*/ | |
function matchString (search: string, mainStrings?: string[], ops?: MatchStringOptions): string | null { | |
const { bestMatchIndex, bestMatch: { rating } } = findBestMatch(search, mainStrings) | |
if (rating < ops?.minRating) return null | |
return mainStrings[bestMatchIndex] | |
} | |
/** | |
* Ask for confirmation before proceeding | |
* @param {Message} message Discord.js message object | |
* @param {string} confirmationMessage Ask for confirmation | |
* @param {ConfirmationOptions} [options] Options | |
* @param {string} [options.confirmMessage] Edit the message upon confirmation | |
* @param {string | MessageEmbed} [options.denyMessage] Edit the message upon denial | |
* @param {number} options.time Timeout | |
* @param {boolean} [options.keepReactions] Keep reactions after reacting | |
* @param {boolean} [options.deleteAfterReaction] Delete the message after reaction (takes priority over all other messages) | |
* @example | |
* const confirmationMessage: string = 'Are you sure you would like to stop the bot?' | |
* const options: ConfirmationOptions = { | |
* confirmMessage: 'Shutting down...', | |
* denyMessage: 'Shutdown cancelled.' | |
* } | |
* | |
* const proceed: boolean = await confirmation(message, confirmationMessage, options) | |
* | |
* if (proceed) process.exit(0) | |
*/ | |
async function confirmation (message: Discord.Message, confirmationMessage: string | Discord.MessageEmbed, options?: ConfirmationOptions): Promise<boolean> { | |
const yesReaction = '✔️' | |
const noReaction = '✖️' | |
const filter = ({ emoji: { name } }: Discord.MessageReaction, { id }: Discord.User): boolean => (name === yesReaction || name === noReaction) && id === message.author.id | |
const msg = await message.channel.send(confirmationMessage) | |
await msg.react(yesReaction) | |
await msg.react(noReaction) | |
const e = (await msg.awaitReactions(filter, { max: 1, time: options?.time ?? 300000 })).first() | |
if (options?.deleteAfterReaction) msg.delete() | |
else if (!options?.keepReactions) msg.reactions.removeAll() | |
if (e?.emoji?.name === yesReaction) { | |
if (options?.confirmMessage && !options?.deleteAfterReaction) await msg.edit(options?.confirmMessage instanceof Discord.MessageEmbed ? { embed: options?.confirmMessage, content: null } : { embed: null, content: options?.confirmMessage }) | |
return true | |
} else { | |
if (options?.denyMessage && !options?.deleteAfterReaction) await msg.edit(options?.denyMessage instanceof Discord.MessageEmbed ? { embed: options?.denyMessage, content: null } : { embed: null, content: options?.denyMessage }) | |
return false | |
} | |
} | |
/** | |
* Capture stdout and stderr while executing a function | |
* @param {Function} callback The callback function to execute | |
* @returns {Promise<CapturedOutput>} stdout, stderr and callback outputs | |
*/ | |
async function captureOutput (callback: Function): Promise<CapturedOutput> { | |
return await new Promise((resolve, reject) => { | |
const oldProcess = { ...process } | |
let stdout = '' | |
let stderr = '' | |
// overwrite stdout write function | |
process.stdout.write = (str: string) => { | |
stdout += str | |
return true | |
} | |
// overwrite stderr write function | |
process.stderr.write = (str: string) => { | |
stderr += str | |
return true | |
} | |
try { | |
const c = callback() | |
delete process.stdout.write | |
process.stdout.write = oldProcess.stdout.write | |
delete process.stderr.write | |
process.stderr.write = oldProcess.stderr.write | |
return c | |
.catch((c: Error) => reject({ stdout, stderr, callbackOutput: c })) // eslint-disable-line prefer-promise-reject-errors | |
.then((callbackOutput: any) => resolve({ stdout, stderr, callbackOutput })) | |
} catch (error) { | |
delete process.stdout.write | |
process.stdout.write = oldProcess.stdout.write | |
delete process.stderr.write | |
process.stderr.write = oldProcess.stderr.write | |
return reject({ stdout, stderr, callbackOutput: error }) // eslint-disable-line prefer-promise-reject-errors | |
} | |
}) | |
} | |
interface MatchStringOptions { | |
/** Only return a string if it is a certain % similar */ | |
minRating?: number | |
} | |
interface ConfirmationOptions { | |
/** Edit the message after confirming */ | |
confirmMessage?: string | Discord.MessageEmbed | |
/** Edit the message after denying */ | |
denyMessage?: string | Discord.MessageEmbed | |
/** Delete the message after receiving a reaction */ | |
deleteAfterReaction?: boolean | |
/** Timeout */ | |
time?: number | |
/** Keep the reactions upon reacting */ | |
keepReactions?: boolean | |
} | |
interface CapturedOutput { | |
stdout: string | |
stderr: string | |
callbackOutput: any | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thats very overcomplicated