Skip to content

Instantly share code, notes, and snippets.

@ScottKaye
Last active April 23, 2016 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ScottKaye/03a370ae20c8b7b250840b82d048e6b4 to your computer and use it in GitHub Desktop.
Save ScottKaye/03a370ae20c8b7b250840b82d048e6b4 to your computer and use it in GitHub Desktop.
"use strict";
const vm = require("vm");
const util = require("util");
const Constants = require("../constants");
let helpText = `you can evaluate **JS code and expressions** with \`!eval\`!
:information_source: Just use \`!eval <code>\`.
:arrow_right: \`<code>\` is any kind of code block (text surrounded by \\\`\\\`\\\` or \\\`\\\`). You can also specify syntax highlighting (for \`js\`) - it will not be evaluated.
`;
module.exports = function(Bot) {
let publics = { base, unsafe };
function getCode(content) {
// Get code from between single or triple backticks, with an optional language of `js`
// Then remove all empty items so the final match is at the end of the array
let code = (content.match(/(```(?:js)?([\s\S]*?)```|`(.+?)`)/) || []).filter(Boolean);
code = code.pop() || null;
return code ? code.trim() : null;
}
function prepareEval(unsafe) {
let code = getCode(this.content);
if (!code) {
return Bot.reply(this, helpText);
}
doEval.call(this, code, unsafe).then(output => {
if (output.length > 1500) {
return Bot.reply(this, `:no_entry_sign: Output over 1500 bytes (it's ${ output.length } bytes), not printing.`);
}
Bot.reply(this, `:white_check_mark: Success! ${ output }`);
}).catch(err => {
Bot.reply(this, `:warning: Failed!\n${ err }`);
});
}
function doEval(code, unsafe, retry) {
return new Promise((resolve, reject) => {
// Did the current call fail to fix itself, or is the code empty?
if (!code.length) {
return reject("this code doesn't appear to do anything...");
}
try {
let finalOutput = ["\n"];
let sandbox = {};
let output = [];
let evalled = false;
// General-purpose logger
function gpLog() {
let vals = Object.keys(this).map(k => JSON.stringify(this[k], null, " "));
output.push.apply(output, vals);
}
// Internal, non-enumerable sandbox objects
Object.defineProperty(sandbox, "console", {
get: () => ({
error: function() { gpLog.call(arguments); },
info: function() { gpLog.call(arguments); },
log: function() { gpLog.call(arguments); },
warn: function() { gpLog.call(arguments); }
})
});
// Run code in strict mode
if (unsafe) {
sandbox = eval(`"use strict"; ${ code }`);
}
else {
vm.runInNewContext(`"use strict"; ${ code }`, sandbox, {
filename: "repl.js",
timeout: 2000
});
}
if (Object.keys(sandbox).length) {
let out = util.inspect(sandbox, { depth: null });
// Just show the value of "__expr" if it's the only one there
if (retry && Object.keys(sandbox).length === 1) {
out = util.inspect(sandbox.__expr, { depth: null });
}
// If there's something to print
if (out.length) {
finalOutput.push(":zap: **Evaluated:**\n```\n" + out + "\n```");
evalled = true;
}
}
if (output.length) {
let p = output.join(" ");
finalOutput.push(":notepad_spiral: **Logged:**\n```\n" + p + "\n```");
}
// If the expression did not evaluate, try again as an expression
if (!retry && !evalled) {
doEval("var __expr = eval(`" + code.replace(/`/g, "\\`") + "`);", unsafe, true)
.then(resolve)
.catch(reject);
}
else {
return resolve(finalOutput.join("\n"));
}
}
catch(ex) {
return reject(`:red_circle: ${ ex }`);
}
});
}
function base(args) {
prepareEval.call(this, false);
}
function unsafe(args) {
prepareEval.call(this, true);
}
unsafe.restrictTo = [Constants.WRANGLER, "MORE_IDS"];
return publics;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment