Skip to content

Instantly share code, notes, and snippets.

@Benricheson101
Last active February 14, 2021 23:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Benricheson101/db14f1b258428d2f4e112bc9b00b4b56 to your computer and use it in GitHub Desktop.
Save Benricheson101/db14f1b258428d2f4e112bc9b00b4b56 to your computer and use it in GitHub Desktop.
Overcomplicated Discord.js eval command
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
}
});
}
// 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
}
@KarateWumpus
Copy link

Thats very overcomplicated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment