progdisc/AwesomeBot

View on GitHub
commands/eval/eval.js

Summary

Maintainability
A
3 hrs
Test Coverage
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
const ReplitClient = require('replit-client');
const gentoken = require('./gentoken');

global.XMLHttpRequest = XMLHttpRequest;

// list of langs:
// https://github.com/replit/ReplitClient.js#replitclienthostname-port-language-token
const availableLanguages = [
  'c',
  'cpp',
  'cpp11',
  'csharp',
  'fsharp',
  'go',
  'java',
  'lua',
  'nodejs',
  'php',
  'python',
  'python3',
  'ruby',
  'rust',
  'swift',
];

// map usual lang names to replit names
const langAliases = {
  'c#': 'csharp',
  'c++': 'cpp',
  'c++11': 'cpp11',
  'f#': 'fsharp',
  js: 'nodejs',
  javascript: 'nodejs',
  py2: 'python',
  py: 'python3',
  py3: 'python3',
  rb: 'ruby',
};

const validateLang = (lang) => {
  if (typeof lang !== 'string') {
    return false;
  }

  const l = langAliases[lang.toLowerCase()] || lang.toLowerCase();

  if (availableLanguages.includes(l)) {
    return l;
  }
  return false;
};

module.exports = {
  usage: [
    'eval <lang> <code> - run code in repl.it',
    'eval - show available languages',
  ],

  run: (bot, message, cmdArgs) => {
    if (!cmdArgs) {
      const langList = availableLanguages.reduce((prev, x) => {
        let langEntry = `${prev}${prev ? '\n' : ''}- ${x}`;
        const aliases = Object.keys(langAliases)
          .filter(alias => langAliases[alias] === x)
          .join(', ');
        if (aliases) {
          langEntry += ` (${aliases})`;
        }
        return langEntry;
      }, '');
      message.reply(`available languages:\n\`\`\`\n${langList}\n\`\`\`\n`);
      return;
    }

    // parsing the messages against regex to obtain the lang and code
    let rxLangs = Object.keys(langAliases)
                        .map((v) => {
                          if (v === 'c++') {
                            return 'c\\+\\+';
                          } else if (v === 'c++11') {
                            return 'c\\+\\+11';
                          }
                          return v;
                        });
    rxLangs.push(...availableLanguages);
    rxLangs = rxLangs.sort((a, b) => b.length - a.length).join('|');
    const rx = new RegExp('^(`{0,3})(' + rxLangs + ')\\s*((.|\\s)+)(\\1)$', 'gi');
    const argsArr = rx.exec(cmdArgs);
    let lang = argsArr[2].toLowerCase();
    const code = (new RegExp('^(`{0,3})((' + rxLangs + '|.{0})\\s)?\\s*((.|\\s)+)(\\1)$', 'gi')).exec(argsArr[3])[4];
    lang = validateLang(lang);
    if (!lang) {
      message.reply('Sorry, I don\'t know that language!');
      return;
    }

    const apiToken = bot.settings.tokens.replit || process.env.REPLIT_TOKEN;
    const repl = new ReplitClient(
      'api.repl.it',
      80,
      lang, gentoken(apiToken));

    message.channel.sendMessage('⏲ evaluating...')
      .then((evalMsg) => {
        let newContent = '';
        repl.evaluateOnce(
          code, {
            stdout: (output) => {
              newContent += `Code output:\n\`\`\`\n${output}\n\`\`\`\n`;
            },
          }).then(
            (result) => {
              if (result.error) {
                newContent += `Error:\n\`\`\`${result.error}\`\`\`\n`;
              } else {
                newContent += `Result:\n\`\`\`${result.data}\`\`\`\n`;
              }
              evalMsg.edit(newContent);
            },
            (error) => {
              newContent += `Error connecting to repl.it!\`\`\`${error}\`\`\`\n`;
              evalMsg.edit(newContent);
              console.error(error);
            });
      })
      .catch(console.error);
  },
};