diff --git a/lib/commands/export.js b/lib/commands/export.js new file mode 100644 index 00000000..3915ac9a --- /dev/null +++ b/lib/commands/export.js @@ -0,0 +1,127 @@ +'use strict'; +var path = require('path'); + +var _ = require('underscore'); + +var h = require('../helper'); +var file = require('../file'); +var chalk = require('../chalk'); +var config = require('../config'); +var log = require('../log'); +var Queue = require('../queue'); +var core = require('../core'); +var session = require('../session'); +var md5 = require('md5'); + +const cmd = { + command: 'export [keyword]', + aliases: ['pulls'], + desc: 'Download submission code', + builder: function(yargs) { + return yargs + .option('o', { + alias: 'outdir', + type: 'string', + describe: 'Where to save submission code', + default: '.' + }) + .positional('keyword', { + type: 'string', + default: '', + describe: 'Download specific question by id' + }) + .example(chalk.yellow('leetcode export -o mydir'), 'Download all to folder mydir') + .example(chalk.yellow('leetcode export 1'), 'Download cpp submission of question 1'); + } +}; + +function doTask(problem, queue, cb) { + const argv = queue.ctx.argv; + + function onTaskDone(e, msg) { + // NOTE: msg color means different purpose: + // - red: error + // - green: accepted, fresh download + // - white: existed already, skip download + log.printf('[%=4s] %-60s %s', problem.fid, problem.name, (e ? chalk.red('ERROR: ' + (e.msg || e)) : msg)); + if (cb) cb(e); + } + + core.getProblem(problem.fid, function(e, problem) { + if (e) return cb(e); + exportSubmissions(problem, argv, onTaskDone); + }); +} + +function exportSubmissions(problem, argv, cb) { + core.getSubmissions(problem, function(e, submissions) { + if (e) return cb(e); + if (submissions.length === 0) { + return cb('No submissions?'); + } + + // get obj list contain required filetype + submissions = submissions.filter(x => x.status_display === 'Accepted'); + if (submissions.length === 0) { + return cb(null, "No accepted code"); + } + + + const basename = file.fmt(config.file.export, problem); + const f = path.join(argv.outdir, basename); + file.mkdir(argv.outdir); + + const data = _.pick(problem, + "fid", + "name", + "slug", + "link", + "locked", + "percent", + "level", + "category", + "companies", + "tags", + "desc" + ); + data.submissions = {}; + + for (let i = 0; i < submissions.length; i++) { + let submission = submissions[i]; + var md5sum = md5(submission.code); + data.submissions[md5sum] = { + "timestamp": h.timeformat(submission.timestamp), + "code": submission.code + }; + } + + file.write(f, JSON.stringify(data)); + cb(null, chalk.green.underline(f)); + }); +} + +cmd.handler = function(argv) { + session.argv = argv; + const q = new Queue(null, { + argv: argv + }, doTask); + + if (argv.keyword) { + core.getProblem(argv.keyword, function(e, problem) { + if (e) { + return log.fail(e); + } + if (problem.state === 'ac') { + q.addTask(problem).run(); + } + }); + return; + } + core.getProblems(function(e, problems) { + if (e) return log.fail(e); + problems = problems.filter(x => x.state === 'ac'); + q.addTasks(problems).run(); + }); +}; + +module.exports = cmd; \ No newline at end of file diff --git a/lib/commands/list.js b/lib/commands/list.js index c010de86..9c6bf491 100644 --- a/lib/commands/list.js +++ b/lib/commands/list.js @@ -73,7 +73,7 @@ cmd.handler = function(argv) { problem.fid, problem.name, h.prettyLevel(problem.level), - problem.percent.toFixed(2)); + problem.percent && problem.percent.toFixed(2)); if (argv.extra) { let badges = [problem.category]; diff --git a/lib/commands/show.js b/lib/commands/show.js index 7c66204a..a42e37b3 100644 --- a/lib/commands/show.js +++ b/lib/commands/show.js @@ -12,6 +12,9 @@ var log = require('../log'); var config = require('../config'); var core = require('../core'); var session = require('../session'); +var cheerio = require('cheerio'); +var he = require('he'); +var open = require('open') const cmd = { command: 'show [keyword]', @@ -30,12 +33,36 @@ const cmd = { type: 'string', describe: 'Open source code in editor' }) + .option('d', { + alias: 'decode', + type: 'boolean', + default: false, + describe: 'Decode desc to text' + }) + .option('z', { + alias: 'translate', + type: 'boolean', + default: false, + describe: 'Translate desc' + }) .option('g', { alias: 'gen', type: 'boolean', default: false, describe: 'Generate source code' }) + .option('v', { + alias: 'view', + type: 'boolean', + default: false, + describe: 'view content in browser' + }) + .option('m', { + alias: 'markdown', + type: 'boolean', + default: false, + describe: 'show url as markdown' + }) .option('l', { alias: 'lang', type: 'string', @@ -137,6 +164,15 @@ function showProblem(problem, argv) { } } + if (argv.view) { + open(problem.link); + } + + if (argv.markdown) { + log.printf('- [ ] %s.%s %s', problem.fid, problem.translatedName, problem.link); + return; + } + log.printf('[%s] %s %s', problem.fid, problem.name, (problem.starred ? chalk.yellow(icon.like) : icon.empty)); log.info(); @@ -162,7 +198,18 @@ function showProblem(problem, argv) { log.printf('* Testcase Example: %s', chalk.yellow(util.inspect(problem.testcase))); log.info(); - log.info(problem.desc); + if (argv.decode) { + problem.desc = problem.desc.replace(/<\/sup>/gm, '').replace(//gm, '^'); + problem.desc = he.decode(cheerio.load(problem.desc).root().text()); + + problem.translatedDesc = problem.translatedDesc.replace(/<\/sup>/gm, '').replace(//gm, '^'); + problem.translatedDesc = he.decode(cheerio.load(problem.translatedDesc).root().text()); + } + if (config.translate.enable || argv.translate) { + log.info(problem.translatedDesc); + } else { + log.info(problem.desc); + } } cmd.handler = function(argv) { diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 56f5ed04..4c61eac8 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -60,29 +60,13 @@ cmd.handler = function(argv) { const result = results[0]; printResult(result, 'state'); - printLine(result, '%d/%d cases passed (%s)', - result.passed, result.total, result.runtime); + printLine(result, '%d/%d cases passed (%s)', result.passed, result.total, result.runtime); if (result.ok) { session.updateStat('ac', 1); session.updateStat('ac.set', problem.fid); - core.getSubmission({id: result.id}, function(e, submission) { - if (e || !submission || !submission.distributionChart) - return log.warn('Failed to get submission beat ratio.'); - - const lang = submission.distributionChart.lang; - const scores = submission.distributionChart.distribution; - const myRuntime = parseFloat(result.runtime); - - let ratio = 0.0; - for (let score of scores) { - if (parseFloat(score[0]) >= myRuntime) - ratio += parseFloat(score[1]); - } - - printLine(result, 'Your runtime beats %d %% of %s submissions', - ratio.toFixed(2), lang); - }); + printLine(result, 'Your cpu runtime (%s) beats %d %% of %s submissions', result.runtime, result.runtime_percentile.toFixed(2), result.lang); + printLine(result, 'Your memory runtime (%s) beats %d %% of %s submissions', result.memory, result.memory_percentile.toFixed(2), result.lang); } else { printResult(result, 'error'); printResult(result, 'testcase'); diff --git a/lib/config.js b/lib/config.js index 373b9f0f..b0d73b2e 100644 --- a/lib/config.js +++ b/lib/config.js @@ -59,7 +59,8 @@ const DEFAULT_CONFIG = { }, file: { show: '${fid}.${slug}', - submission: '${fid}.${slug}.${sid}.${ac}' + submission: '${fid}.${slug}.${sid}.${ac}', + export: '${fid}.${slug}.json' }, color: { enable: true, diff --git a/lib/core.js b/lib/core.js index 74362f78..4f80e98f 100644 --- a/lib/core.js +++ b/lib/core.js @@ -89,7 +89,7 @@ core.getProblem = function(keyword, cb) { keyword = Number(keyword) || keyword; const problem = problems.find(function(x) { - return x.fid === keyword || x.name === keyword || x.slug === keyword; + return Number(x.fid) === keyword || x.fid === keyword || x.name === keyword || x.slug === keyword; }); if (!problem) return cb('Problem not found!'); core.next.getProblem(problem, cb); @@ -116,6 +116,7 @@ core.exportProblem = function(problem, opts) { data.comment = h.langToCommentStyle(data.lang); data.percent = data.percent.toFixed(2); data.testcase = util.inspect(data.testcase || ''); + data.currentTime = h.timeformat(); if (opts.tpl === 'detailed') { // NOTE: wordwrap internally uses '\n' as EOL, so here we have to diff --git a/lib/helper.js b/lib/helper.js index 8806086e..26c6a966 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('underscore'); var ora = require('ora'); +var moment = require('moment'); var file = require('./file'); @@ -31,21 +32,21 @@ function getUnit(units, v) { const LANGS = [ {lang: 'bash', ext: '.sh', style: '#'}, - {lang: 'c', ext: '.c', style: 'c'}, - {lang: 'cpp', ext: '.cpp', style: 'c'}, - {lang: 'csharp', ext: '.cs', style: 'c'}, - {lang: 'golang', ext: '.go', style: 'c'}, - {lang: 'java', ext: '.java', style: 'c'}, - {lang: 'javascript', ext: '.js', style: 'c'}, - {lang: 'kotlin', ext: '.kt', style: 'c'}, + {lang: 'c', ext: '.c', style: '//'}, + {lang: 'cpp', ext: '.cpp', style: '//'}, + {lang: 'csharp', ext: '.cs', style: '//'}, + {lang: 'golang', ext: '.go', style: '//'}, + {lang: 'java', ext: '.java', style: '//'}, + {lang: 'javascript', ext: '.js', style: '//'}, + {lang: 'kotlin', ext: '.kt', style: '//'}, {lang: 'mysql', ext: '.sql', style: '--'}, - {lang: 'php', ext: '.php', style: 'c'}, + {lang: 'php', ext: '.php', style: '//'}, {lang: 'python', ext: '.py', style: '#'}, {lang: 'python3', ext: '.py', style: '#'}, {lang: 'ruby', ext: '.rb', style: '#'}, - {lang: 'rust', ext: '.rs', style: 'c'}, - {lang: 'scala', ext: '.scala', style: 'c'}, - {lang: 'swift', ext: '.swift', style: 'c'} + {lang: 'rust', ext: '.rs', style: '//'}, + {lang: 'scala', ext: '.scala', style: '//'}, + {lang: 'swift', ext: '.swift', style: '//'} ]; const h = {}; @@ -55,7 +56,7 @@ h.KEYS = { stat: '../stat', plugins: '../../plugins', problems: 'problems', - problem: p => p.fid + '.' + p.slug + '.' + p.category + problem: p => p.fid + '.' + p.slug }; h.prettyState = function(state) { @@ -186,6 +187,13 @@ h.spin = function(s) { return ora(require('./chalk').gray(s)).start(); }; +h.timeformat = function(date = 0) { + if (date == 0) { + return moment().format('YYYY-MM-DD HH:mm:ss'); + } + return moment.unix(date).format('YYYY-MM-DD HH:mm:ss'); +}; + const COLORS = { blue: {fg: 'white', bg: 'bgBlue'}, cyan: {fg: 'white', bg: 'bgCyan'}, diff --git a/lib/plugins/company.js b/lib/plugins/company.js new file mode 100644 index 00000000..ac48974e --- /dev/null +++ b/lib/plugins/company.js @@ -0,0 +1,1187 @@ +var Plugin = require('../plugin'); + +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/company.md +// +var plugin = new Plugin(100, 'company', '2017.12.18', + 'Plugin to query by company for free user.'); + +// NOTE: those data is collected from different websites that has similar questions: +// * careercup.com +// * lintcode.com +// * 1point3acres.com +// * other results from google search +var COMPONIES = { + '1': ['adobe', 'airbnb', 'amazon', 'apple', 'bloomberg', 'dropbox', 'facebook', 'linkedin', 'microsoft', 'uber', 'yahoo', 'yelp'], + '2': ['adobe', 'airbnb', 'amazon', 'bloomberg', 'microsoft'], + '3': ['adobe', 'amazon', 'bloomberg', 'yelp'], + '4': ['adobe', 'apple', 'dropbox', 'google', 'microsoft', 'yahoo', 'zenefits'], + '5': ['amazon', 'bloomberg', 'microsoft'], + '7': ['apple', 'bloomberg'], + '8': ['amazon', 'bloomberg', 'microsoft', 'uber'], + + '10': ['airbnb', 'facebook', 'google', 'twitter', 'uber'], + '11': ['bloomberg'], + '12': ['twitter'], + '13': ['bloomberg', 'facebook', 'microsoft', 'uber', 'yahoo'], + '14': ['yelp'], + '15': ['adobe', 'amazon', 'bloomberg', 'facebook', 'microsoft'], + '16': ['bloomberg'], + '17': ['amazon', 'dropbox', 'facebook', 'google', 'uber'], + '18': ['linkedin'], + '20': ['airbnb', 'amazon', 'bloomberg', 'facebook', 'google', 'microsoft', 'twitter', 'zenefits'], + '21': ['amazon', 'apple', 'linkedin', 'microsoft'], + '22': ['google', 'uber', 'zenefits'], + '23': ['airbnb', 'amazon', 'facebook', 'google', 'linkedin', 'microsoft', 'twitter', 'uber'], + '24': ['bloomberg', 'microsoft', 'uber'], + '25': ['facebook', 'microsoft'], + '26': ['bloomberg', 'facebook', 'microsoft'], + '28': ['apple', 'facebook', 'microsoft', 'pocketgems'], + '31': ['google'], + '33': ['bloomberg', 'facebook', 'linkedin', 'microsoft', 'uber'], + '34': ['linkedin'], + '36': ['apple', 'snapchat', 'uber'], + '37': ['snapchat', 'uber'], + '38': ['facebook'], + '39': ['snapchat', 'uber'], + '40': ['snapchat'], + '42': ['amazon', 'apple', 'bloomberg', 'google', 'twitter', 'zenefits'], + '43': ['facebook', 'twitter'], + '44': ['facebook', 'google', 'snapchat', 'twitter', 'twosigma'], + '46': ['linkedin', 'microsoft'], + '47': ['linkedin', 'microsoft'], + '48': ['amazon', 'apple', 'microsoft'], + '49': ['amazon', 'bloomberg', 'facebook', 'uber', 'yelp'], + '50': ['bloomberg', 'facebook', 'google', 'linkedin'], + '52': ['zenefits'], + '53': ['bloomberg', 'linkedin', 'microsoft'], + '54': ['google', 'microsoft', 'uber'], + '55': ['microsoft'], + '56': ['bloomberg', 'facebook', 'google', 'linkedin', 'microsoft', 'twitter', 'yelp'], + '57': ['facebook', 'google', 'linkedin'], + '60': ['twitter'], + '62': ['bloomberg'], + '63': ['bloomberg'], + '65': ['linkedin'], + '66': ['google'], + '67': ['facebook'], + '68': ['airbnb', 'facebook', 'linkedin'], + '69': ['apple', 'bloomberg', 'facebook'], + '70': ['adobe', 'apple'], + '71': ['facebook', 'microsoft'], + '73': ['amazon', 'microsoft'], + '75': ['facebook', 'microsoft', 'pocketgems'], + '76': ['facebook', 'linkedin', 'snapchat', 'uber'], + '78': ['amazon', 'bloomberg', 'facebook', 'uber'], + '79': ['bloomberg', 'facebook', 'microsoft'], + '80': ['facebook'], + '85': ['facebook'], + '88': ['bloomberg', 'facebook', 'microsoft'], + '89': ['amazon'], + '90': ['facebook'], + '91': ['facebook', 'microsoft', 'uber'], + '94': ['microsoft'], + '96': ['snapchat'], + '98': ['amazon', 'bloomberg', 'facebook', 'microsoft'], + + '100': ['bloomberg'], + '101': ['bloomberg', 'linkedin', 'microsoft'], + '102': ['amazon', 'apple', 'bloomberg', 'facebook', 'linkedin', 'microsoft'], + '103': ['bloomberg', 'linkedin', 'microsoft'], + '104': ['apple', 'linkedin', 'uber', 'yahoo'], + '105': ['bloomberg'], + '106': ['microsoft'], + '108': ['airbnb'], + '109': ['zenefits'], + '110': ['bloomberg'], + '112': ['microsoft'], + '113': ['bloomberg'], + '114': ['microsoft'], + '116': ['microsoft'], + '117': ['bloomberg', 'facebook', 'microsoft'], + '118': ['apple', 'twitter'], + '119': ['amazon'], + '121': ['amazon', 'bloomberg', 'facebook', 'microsoft', 'uber'], + '122': ['bloomberg'], + '124': ['baidu', 'microsoft'], + '125': ['facebook', 'microsoft', 'uber', 'zenefits'], + '126': ['amazon', 'yelp'], + '127': ['amazon', 'facebook', 'linkedin', 'snapchat', 'yelp'], + '128': ['facebook', 'google'], + '131': ['bloomberg'], + '133': ['facebook', 'google', 'pocketgems', 'uber'], + '136': ['airbnb', 'palantir'], + '138': ['amazon', 'bloomberg', 'microsoft', 'uber'], + '139': ['amazon', 'bloomberg', 'facebook', 'google', 'pocketgems', 'uber', 'yahoo'], + '140': ['dropbox', 'google', 'snapchat', 'twitter', 'uber'], + '141': ['amazon', 'bloomberg', 'microsoft', 'yahoo'], + '146': ['amazon', 'bloomberg', 'facebook', 'google', 'microsoft', 'palantir', 'snapchat', 'twitter', 'uber', 'yahoo', 'zenefits'], + '149': ['apple', 'linkedin', 'twitter'], + '150': ['linkedin'], + '151': ['apple', 'bloomberg', 'microsoft', 'snapchat', 'yelp'], + '152': ['linkedin'], + '153': ['microsoft'], + '155': ['amazon', 'bloomberg', 'google', 'snapchat', 'uber', 'zenefits'], + '156': ['linkedin'], + '157': ['facebook'], + '158': ['bloomberg', 'facebook', 'google'], + '159': ['google'], + '160': ['airbnb', 'amazon', 'bloomberg', 'microsoft'], + '161': ['facebook', 'snapchat', 'twitter', 'uber'], + '162': ['google', 'microsoft'], + '163': ['google'], + '165': ['apple', 'microsoft'], + '166': ['google'], + '167': ['amazon'], + '168': ['facebook', 'microsoft', 'zenefits'], + '169': ['adobe', 'zenefits'], + '170': ['linkedin'], + '171': ['microsoft', 'uber'], + '172': ['bloomberg'], + '173': ['facebook', 'google', 'linkedin', 'microsoft'], + '174': ['microsoft'], + '186': ['amazon', 'microsoft', 'uber'], + '187': ['linkedin'], + '189': ['amazon', 'bloomberg', 'microsoft'], + '190': ['airbnb', 'apple'], + '191': ['apple', 'microsoft'], + '195': ['adobe'], + '198': ['airbnb', 'linkedin'], + '199': ['amazon'], + '200': ['amazon', 'facebook', 'google', 'microsoft', 'zenefits'], + '202': ['airbnb', 'twitter', 'uber'], + '204': ['amazon', 'microsoft'], + '205': ['linkedin'], + '206': ['adobe', 'amazon', 'apple', 'bloomberg', 'facebook', 'microsoft', 'snapchat', 'twitter', 'uber', 'yahoo', 'yelp', 'zenefits'], + '207': ['apple', 'uber', 'yelp', 'zenefits'], + '208': ['bloomberg', 'facebook', 'google', 'microsoft', 'twitter', 'uber'], + '209': ['facebook'], + '210': ['facebook', 'zenefits'], + '211': ['facebook'], + '212': ['airbnb', 'google', 'microsoft'], + '213': ['microsoft'], + '214': ['google', 'pocketgems'], + '215': ['amazon', 'apple', 'bloomberg', 'facebook', 'microsoft', 'pocketgems'], + '217': ['airbnb', 'palantir', 'yahoo'], + '218': ['facebook', 'google', 'microsoft', 'twitter', 'yelp'], + '219': ['airbnb', 'palantir'], + '220': ['airbnb', 'palantir'], + '221': ['airbnb', 'apple', 'facebook'], + '224': ['google'], + '225': ['bloomberg'], + '227': ['airbnb'], + '228': ['google'], + '229': ['zenefits'], + '230': ['bloomberg', 'google', 'uber'], + '231': ['google'], + '232': ['bloomberg', 'microsoft'], + '234': ['amazon', 'facebook'], + '235': ['amazon', 'facebook', 'microsoft', 'twitter'], + '236': ['amazon', 'apple', 'facebook', 'linkedin', 'microsoft'], + '237': ['adobe', 'apple', 'microsoft'], + '238': ['amazon', 'apple', 'facebook', 'linkedin', 'microsoft'], + '239': ['amazon', 'google', 'zenefits'], + '240': ['amazon', 'apple', 'google'], + '242': ['amazon', 'uber', 'yelp'], + '243': ['linkedin'], + '244': ['linkedin'], + '245': ['linkedin'], + '246': ['google'], + '247': ['google'], + '249': ['google', 'uber'], + '251': ['airbnb', 'google', 'twitter', 'zenefits'], + '252': ['facebook'], + '253': ['facebook', 'google', 'snapchat'], + '254': ['linkedin', 'uber'], + '255': ['zenefits'], + '256': ['linkedin'], + '257': ['apple', 'facebook', 'google'], + '258': ['adobe', 'microsoft'], + '259': ['google'], + '261': ['facebook', 'google', 'zenefits'], + '262': ['uber'], + '265': ['facebook'], + '266': ['bloomberg', 'google', 'uber'], + '268': ['bloomberg', 'microsoft'], + '269': ['airbnb', 'facebook', 'google', 'pocketgems', 'snapchat', 'twitter'], + '270': ['google', 'microsoft', 'snapchat'], + '271': ['google'], + '272': ['google'], + '273': ['facebook', 'microsoft'], + '274': ['bloomberg', 'facebook', 'google'], + '275': ['facebook'], + '276': ['google'], + '277': ['facebook', 'linkedin'], + '278': ['facebook'], + '279': ['google'], + '280': ['google'], + '281': ['google'], + '282': ['facebook', 'google'], + '283': ['bloomberg', 'facebook'], + '284': ['apple', 'google', 'yahoo'], + '285': ['facebook', 'microsoft', 'pocketgems'], + '286': ['facebook', 'google'], + '287': ['bloomberg'], + '288': ['google'], + '289': ['dropbox', 'google', 'snapchat', 'twosigma'], + '290': ['dropbox', 'uber'], + '291': ['dropbox', 'uber'], + '292': ['adobe'], + '293': ['google'], + '294': ['google'], + '295': ['google'], + '296': ['twitter'], + '297': ['amazon', 'bloomberg', 'facebook', 'google', 'linkedin', 'microsoft', 'uber', 'yahoo'], + '298': ['google'], + '300': ['microsoft'], + '301': ['facebook'], + '302': ['google'], + '303': ['palantir'], + '305': ['google'], + '308': ['google'], + '309': ['google'], + '310': ['google'], + '311': ['facebook', 'linkedin'], + '312': ['google', 'snapchat'], + '313': ['google'], + '314': ['facebook', 'google', 'snapchat'], + '315': ['google'], + '316': ['google'], + '317': ['google', 'zenefits'], + '318': ['google'], + '320': ['google'], + '321': ['google'], + '323': ['google', 'twitter'], + '324': ['google'], + '325': ['facebook', 'palantir'], + '326': ['google'], + '327': ['google'], + '329': ['google'], + '330': ['google'], + '331': ['google'], + '332': ['google'], + '333': ['microsoft'], + '334': ['facebook'], + '336': ['airbnb', 'google'], + '337': ['uber'], + '339': ['linkedin'], + '340': ['google'], + '341': ['facebook', 'google', 'twitter'], + '342': ['twosigma'], + '345': ['google'], + '346': ['google'], + '347': ['pocketgems', 'yelp'], + '348': ['google', 'microsoft'], + '349': ['twosigma'], + '351': ['google'], + '353': ['google'], + '354': ['google'], + '355': ['amazon', 'twitter'], + '356': ['google'], + '357': ['google'], + '358': ['google'], + '359': ['google'], + '360': ['google'], + '361': ['google'], + '362': ['dropbox', 'google'], + '363': ['google'], + '364': ['linkedin'], + '365': ['microsoft'], + '366': ['linkedin'], + '367': ['linkedin'], + '368': ['google'], + '369': ['google'], + '370': ['google'], + '373': ['google', 'uber'], + '374': ['google'], + '375': ['google'], + '377': ['facebook', 'google', 'snapchat'], + '378': ['google', 'twitter'], + '379': ['google'], + '380': ['amazon', 'facebook', 'google', 'pocketgems', 'twitter', 'uber', 'yelp'], + '381': ['yelp'], + '382': ['google'], + '383': ['apple'], + '385': ['airbnb'], + '386': ['bloomberg'], + '387': ['amazon', 'bloomberg', 'microsoft'], + '388': ['google'], + '389': ['google'], + '391': ['google'], + '393': ['google'], + '394': ['google', 'yelp'], + '395': ['baidu'], + '396': ['amazon'], + '397': ['baidu', 'google'], + '398': ['facebook'], + '399': ['google'], + '400': ['google'], + '401': ['google'], + '402': ['google', 'snapchat'], + '403': ['snapchat'], + '404': ['facebook'], + '406': ['google'], + '407': ['google', 'twitter'], + '408': ['google'], + '409': ['google'], + '410': ['baidu', 'facebook'], + '411': ['google'], + '413': ['baidu'], + '414': ['amazon'], + '415': ['airbnb', 'google'], + '416': ['ebay'], + '417': ['google'], + '418': ['google'], + '419': ['microsoft'], + '421': ['google'], + '422': ['google'], + '424': ['pocketgems'], + '425': ['google'], + '432': ['uber'], + '433': ['twitter'], + '438': ['amazon'], + '439': ['snapchat'], + '442': ['pocketgems'], + '443': ['bloomberg', 'microsoft', 'snapchat', 'yelp'], + '444': ['google'], + '445': ['bloomberg', 'microsoft'], + '446': ['baidu'], + '447': ['google'], + '448': ['google'], + '449': ['amazon'], + '450': ['uber'], + '451': ['amazon', 'google'], + '452': ['microsoft'], + '453': ['indeed'], + '459': ['amazon', 'google'], + '460': ['amazon', 'google'], + '461': ['facebook'], + '463': ['google'], + '464': ['linkedin'], + '465': ['google'], + '468': ['twitter'], + '469': ['google'], + '471': ['google'], + '474': ['google'], + '475': ['google'], + '477': ['facebook'], + '479': ['yahoo'], + '480': ['google'], + '481': ['google'], + '482': ['google'], + '483': ['google'], + '484': ['google'], + '485': ['google'], + '486': ['google'], + '487': ['google'], + '488': ['baidu'], + '490': ['google'], + '491': ['yahoo'], + '493': ['google'], + '494': ['facebook', 'google'], + '498': ['google'], + '500': ['mathworks'], + '501': ['google'], + '503': ['google'], + '505': ['google'], + '506': ['google'], + '508': ['amazon'], + '513': ['microsoft'], + '514': ['google'], + '515': ['linkedin'], + '516': ['amazon', 'uber'], + '517': ['amazon'], + '520': ['google'], + '521': ['google'], + '522': ['google'], + '523': ['facebook'], + '524': ['google'], + '525': ['facebook'], + '526': ['google'], + '527': ['google', 'snapchat'], + '529': ['amazon'], + '530': ['google'], + '531': ['google'], + '532': ['amazon'], + '533': ['google'], + '534': ['amazon', 'facebook', 'google', 'uber'], + '535': ['amazon', 'facebook', 'google', 'uber'], + '536': ['amazon'], + '537': ['amazon'], + '538': ['amazon'], + '541': ['google'], + '542': ['google'], + '543': ['facebook', 'google'], + '544': ['google'], + '545': ['amazon', 'google'], + '547': ['bloomberg', 'twosigma'], + '548': ['alibaba'], + '549': ['google'], + '551': ['google'], + '552': ['google'], + '553': ['amazon'], + '554': ['facebook'], + '555': ['alibaba'], + '556': ['bloomberg'], + '557': ['zappos'], + '560': ['google'], + '562': ['google'], + '563': ['indeed'], + '564': ['yelp'], + '566': ['mathworks'], + '567': ['microsoft'], + '568': ['google'], + '569': ['google'], + '570': ['bloomberg'], + '572': ['ebay', 'facebook', 'google'], + '576': ['baidu'], + '578': ['facebook'], + '579': ['amazon'], + '580': ['twitter'], + '581': ['google'], + '582': ['bloomberg'], + '583': ['google'], + '585': ['twitter'], + '586': ['twitter'], + '587': ['google'], + '591': ['microsoft'], + '597': ['facebook'], + '599': ['yelp'], + '602': ['facebook'], + '604': ['google'], + '605': ['linkedin'], + '606': ['amazon'], + '608': ['twitter'], + '616': ['google'], + '617': ['amazon'], + '621': ['facebook'], + '631': ['microsoft'], + '633': ['linkedin'], + '635': ['snapchat'], + '636': ['facebook', 'uber'], + '637': ['facebook'], + '638': ['google'], + '639': ['facebook'], + '640': ['amazon'], + '642': ['facebook', 'microsoft'], + '643': ['google'], + '644': ['google'], + '645': ['amazon'], + '646': ['amazon'], + '647': ['facebook', 'linkedin'], + '648': ['uber'], + '650': ['microsoft'], + '651': ['google', 'microsoft'], + '652': ['google'], + '653': ['facebook'], + '654': ['microsoft'], + '656': ['google'], + '657': ['google'], + '658': ['google'], + '659': ['google'], + '661': ['amazon'], + '662': ['amazon'], + '663': ['amazon'], + '665': ['google'], + '667': ['google'], + '668': ['google'], + '669': ['bloomberg'], + '670': ['facebook'], + '671': ['linkedin'], + '672': ['microsoft'], + '673': ['facebook'], + '674': ['facebook'], + '675': ['amazon'], + '676': ['google'], + '679': ['google'], + '680': ['facebook'], + '681': ['google'], + '682': ['amazon'], + '683': ['google'], + '684': ['google'], + '685': ['google'], + '686': ['google'], + '687': ['google'], + '689': ['facebook', 'google'], + '690': ['uber'], + '692': ['amazon', 'bloomberg', 'uber', 'yelp'], + '694': ['amazon'], + '698': ['linkedin'], + '699': ['uber'], + '711': ['amazon'], + '714': ['bloomberg', 'facebook'], + '716': ['linkedin'], + '719': ['google'], + '721': ['facebook'], + '722': ['microsoft'], + '725': ['amazon'], + '726': ['google'], + '727': ['google'], + '729': ['google'], + '730': ['linkedin'], + '731': ['google'] +}; + +var TAGS = { + '1': ['array', 'hash-table'], + '2': ['linked-list', 'math'], + '3': ['hash-table', 'string', 'two-pointers'], + '4': ['array', 'binary-search', 'divide-and-conquer'], + '5': ['string'], + '6': ['string'], + '7': ['math'], + '8': ['math', 'string'], + '9': ['math'], + + '10': ['backtracking', 'dynamic-programming', 'string'], + '11': ['array', 'two-pointers'], + '12': ['math', 'string'], + '13': ['math', 'string'], + '14': ['string'], + '15': ['array', 'two-pointers'], + '16': ['array', 'two-pointers'], + '17': ['backtracking', 'string'], + '18': ['array', 'hash-table', 'two-pointers'], + '19': ['linked-list', 'two-pointers'], + '20': ['stack', 'string'], + '21': ['linked-list'], + '22': ['backtracking', 'string'], + '23': ['divide-and-conquer', 'heap', 'linked-list'], + '24': ['linked-list'], + '25': ['linked-list'], + '26': ['array', 'two-pointers'], + '27': ['array', 'two-pointers'], + '28': ['string', 'two-pointers'], + '29': ['binary-search', 'math'], + '30': ['hash-table', 'string', 'two-pointers'], + '31': ['array'], + '32': ['dynamic-programming', 'string'], + '33': ['array', 'binary-search'], + '34': ['array', 'binary-search'], + '35': ['array', 'binary-search'], + '36': ['hash-table'], + '37': ['backtracking', 'hash-table'], + '38': ['string'], + '39': ['array', 'backtracking'], + '40': ['array', 'backtracking'], + '41': ['array'], + '42': ['array', 'stack', 'two-pointers'], + '43': ['math', 'string'], + '44': ['backtracking', 'dynamic-programming', 'greedy', 'string'], + '45': ['array', 'greedy'], + '46': ['backtracking'], + '47': ['backtracking'], + '48': ['array'], + '49': ['hash-table', 'string'], + '50': ['binary-search', 'math'], + '51': ['backtracking'], + '52': ['backtracking'], + '53': ['array', 'divide-and-conquer', 'dynamic-programming'], + '54': ['array'], + '55': ['array', 'greedy'], + '56': ['array', 'sort'], + '57': ['array', 'sort'], + '58': ['string'], + '59': ['array'], + '60': ['backtracking', 'math'], + '61': ['linked-list', 'two-pointers'], + '62': ['array', 'dynamic-programming'], + '63': ['array', 'dynamic-programming'], + '64': ['array', 'dynamic-programming'], + '65': ['math', 'string'], + '66': ['array', 'math'], + '67': ['math', 'string'], + '68': ['string'], + '69': ['binary-search', 'math'], + '70': ['dynamic-programming'], + '71': ['stack', 'string'], + '72': ['dynamic-programming', 'string'], + '73': ['array'], + '74': ['array', 'binary-search'], + '75': ['array', 'sort', 'two-pointers'], + '76': ['hash-table', 'string', 'two-pointers'], + '77': ['backtracking'], + '78': ['array', 'backtracking', 'bit-manipulation'], + '79': ['array', 'backtracking'], + '80': ['array', 'two-pointers'], + '81': ['array', 'binary-search'], + '82': ['linked-list'], + '83': ['linked-list'], + '84': ['array', 'stack'], + '85': ['array', 'dynamic-programming', 'hash-table', 'stack'], + '86': ['linked-list', 'two-pointers'], + '87': ['dynamic-programming', 'string'], + '88': ['array', 'two-pointers'], + '89': ['backtracking'], + '90': ['array', 'backtracking'], + '91': ['dynamic-programming', 'string'], + '92': ['linked-list'], + '93': ['backtracking', 'string'], + '94': ['hash-table', 'stack', 'tree'], + '95': ['dynamic-programming', 'tree'], + '96': ['dynamic-programming', 'tree'], + '97': ['dynamic-programming', 'string'], + '98': ['depth-first-search', 'tree'], + '99': ['depth-first-search', 'tree'], + + '100': ['depth-first-search', 'tree'], + '101': ['breadth-first-search', 'depth-first-search', 'tree'], + '102': ['breadth-first-search', 'tree'], + '103': ['breadth-first-search', 'stack', 'tree'], + '104': ['depth-first-search', 'tree'], + '105': ['array', 'depth-first-search', 'tree'], + '106': ['array', 'depth-first-search', 'tree'], + '107': ['breadth-first-search', 'tree'], + '108': ['depth-first-search', 'tree'], + '109': ['depth-first-search', 'linked-list'], + '110': ['depth-first-search', 'tree'], + '111': ['breadth-first-search', 'depth-first-search', 'tree'], + '112': ['depth-first-search', 'tree'], + '113': ['depth-first-search', 'tree'], + '114': ['depth-first-search', 'tree'], + '115': ['dynamic-programming', 'string'], + '116': ['depth-first-search', 'tree'], + '117': ['depth-first-search', 'tree'], + '118': ['array'], + '119': ['array'], + '120': ['array', 'dynamic-programming'], + '121': ['array', 'dynamic-programming'], + '122': ['array', 'greedy'], + '123': ['array', 'dynamic-programming'], + '124': ['depth-first-search', 'tree'], + '125': ['string', 'two-pointers'], + '126': ['array', 'backtracking', 'breadth-first-search', 'string'], + '127': ['breadth-first-search'], + '128': ['array', 'union-find'], + '129': ['depth-first-search', 'tree'], + '130': ['breadth-first-search', 'depth-first-search', 'union-find'], + '131': ['backtracking'], + '132': ['dynamic-programming'], + '133': ['breadth-first-search', 'depth-first-search', 'graph'], + '134': ['greedy'], + '135': ['greedy'], + '136': ['bit-manipulation', 'hash-table'], + '137': ['bit-manipulation'], + '138': ['hash-table', 'linked-list'], + '139': ['dynamic-programming'], + '140': ['backtracking', 'dynamic-programming'], + '141': ['linked-list', 'two-pointers'], + '142': ['linked-list', 'two-pointers'], + '143': ['linked-list'], + '144': ['stack', 'tree'], + '145': ['stack', 'tree'], + '146': ['design'], + '147': ['linked-list', 'sort'], + '148': ['linked-list', 'sort'], + '149': ['hash-table', 'math'], + '150': ['stack'], + '151': ['string'], + '152': ['array', 'dynamic-programming'], + '153': ['array', 'binary-search'], + '154': ['array', 'binary-search'], + '155': ['design', 'stack'], + '156': ['tree'], + '157': ['string'], + '158': ['string'], + '159': ['hash-table', 'string', 'two-pointers'], + '160': ['linked-list'], + '161': ['string'], + '162': ['array', 'binary-search'], + '163': ['array'], + '164': ['sort'], + '165': ['string'], + '166': ['hash-table', 'math'], + '167': ['array', 'binary-search', 'two-pointers'], + '168': ['math'], + '169': ['array', 'bit-manipulation', 'divide-and-conquer'], + '170': ['design', 'hash-table'], + '171': ['math'], + '172': ['math'], + '173': ['design', 'stack', 'tree'], + '174': ['binary-search', 'dynamic-programming'], + '179': ['sort'], + '186': ['string'], + '187': ['bit-manipulation', 'hash-table'], + '188': ['dynamic-programming'], + '189': ['array'], + '190': ['bit-manipulation'], + '191': ['bit-manipulation'], + '198': ['dynamic-programming'], + '199': ['breadth-first-search', 'depth-first-search', 'tree'], + '200': ['breadth-first-search', 'depth-first-search', 'union-find'], + '201': ['bit-manipulation'], + '202': ['hash-table', 'math'], + '203': ['linked-list'], + '204': ['hash-table', 'math'], + '205': ['hash-table'], + '206': ['linked-list'], + '207': ['breadth-first-search', 'depth-first-search', 'graph', 'topological-sort'], + '208': ['design', 'trie'], + '209': ['array', 'binary-search', 'two-pointers'], + '210': ['breadth-first-search', 'depth-first-search', 'graph', 'topological-sort'], + '211': ['backtracking', 'design', 'trie'], + '212': ['backtracking', 'trie'], + '213': ['dynamic-programming'], + '214': ['string'], + '215': ['divide-and-conquer', 'heap'], + '216': ['array', 'backtracking'], + '217': ['array', 'hash-table'], + '218': ['binary-indexed-tree', 'divide-and-conquer', 'heap', 'segment-tree'], + '219': ['array', 'hash-table'], + '220': ['binary-search-tree'], + '221': ['dynamic-programming'], + '222': ['binary-search', 'tree'], + '223': ['math'], + '224': ['math', 'stack'], + '225': ['design', 'stack'], + '226': ['tree'], + '227': ['string'], + '228': ['array'], + '229': ['array'], + '230': ['binary-search', 'tree'], + '231': ['bit-manipulation', 'math'], + '232': ['design', 'stack'], + '233': ['math'], + '234': ['linked-list', 'two-pointers'], + '235': ['tree'], + '236': ['tree'], + '237': ['linked-list'], + '238': ['array'], + '239': ['heap'], + '240': ['binary-search', 'divide-and-conquer'], + '241': ['divide-and-conquer'], + '242': ['hash-table', 'sort'], + '243': ['array'], + '244': ['design', 'hash-table'], + '245': ['array'], + '246': ['hash-table', 'math'], + '247': ['math'], + '248': ['math'], + '249': ['hash-table', 'string'], + '250': ['tree'], + '251': ['design'], + '252': ['sort'], + '253': ['greedy', 'heap', 'sort'], + '254': ['backtracking'], + '255': ['stack', 'tree'], + '256': ['dynamic-programming'], + '257': ['depth-first-search', 'tree'], + '258': ['math'], + '259': ['array', 'two-pointers'], + '260': ['bit-manipulation'], + '261': ['breadth-first-search', 'depth-first-search', 'graph', 'union-find'], + '263': ['math'], + '264': ['dynamic-programming', 'heap', 'math'], + '265': ['dynamic-programming'], + '266': ['hash-table'], + '267': ['backtracking'], + '268': ['array', 'bit-manipulation', 'math'], + '269': ['graph', 'topological-sort'], + '270': ['binary-search', 'tree'], + '271': ['string'], + '272': ['stack', 'tree'], + '273': ['math', 'string'], + '274': ['hash-table', 'sort'], + '275': ['binary-search'], + '276': ['dynamic-programming'], + '277': ['array'], + '278': ['binary-search'], + '279': ['breadth-first-search', 'dynamic-programming', 'math'], + '280': ['array', 'sort'], + '281': ['design'], + '282': ['divide-and-conquer'], + '283': ['array', 'two-pointers'], + '284': ['design'], + '285': ['tree'], + '286': ['breadth-first-search'], + '287': ['array', 'binary-search', 'two-pointers'], + '288': ['design', 'hash-table'], + '289': ['array'], + '290': ['hash-table'], + '291': ['backtracking'], + '293': ['string'], + '294': ['backtracking'], + '295': ['design', 'heap'], + '296': ['math', 'sort'], + '297': ['design', 'tree'], + '298': ['tree'], + '299': ['hash-table'], + '300': ['binary-search', 'dynamic-programming'], + '301': ['breadth-first-search', 'depth-first-search'], + '302': ['binary-search'], + '303': ['dynamic-programming'], + '304': ['dynamic-programming'], + '305': ['union-find'], + '307': ['binary-indexed-tree', 'segment-tree'], + '308': ['binary-indexed-tree', 'segment-tree'], + '309': ['dynamic-programming'], + '310': ['breadth-first-search', 'graph'], + '311': ['hash-table'], + '312': ['divide-and-conquer', 'dynamic-programming'], + '313': ['heap', 'math'], + '314': ['hash-table'], + '315': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree', 'binary-search-tree'], + '316': ['greedy', 'stack'], + '317': ['breadth-first-search'], + '318': ['bit-manipulation'], + '319': ['math'], + '320': ['backtracking', 'bit-manipulation'], + '321': ['dynamic-programming', 'greedy'], + '322': ['dynamic-programming'], + '323': ['breadth-first-search', 'depth-first-search', 'graph', 'union-find'], + '324': ['sort'], + '325': ['hash-table'], + '326': ['math'], + '327': ['divide-and-conquer', 'binary-search-tree'], + '328': ['linked-list'], + '329': ['depth-first-search', 'topological-sort'], + '330': ['greedy'], + '331': ['stack'], + '332': ['depth-first-search', 'graph'], + '333': ['tree'], + '335': ['math'], + '336': ['hash-table', 'string', 'trie'], + '337': ['depth-first-search', 'tree'], + '338': ['bit-manipulation', 'dynamic-programming'], + '339': ['depth-first-search'], + '340': ['hash-table', 'string'], + '341': ['design', 'stack'], + '342': ['bit-manipulation'], + '343': ['dynamic-programming', 'math'], + '344': ['string', 'two-pointers'], + '345': ['string', 'two-pointers'], + '346': ['design', 'queue'], + '347': ['hash-table', 'heap'], + '348': ['design'], + '349': ['binary-search', 'hash-table', 'sort', 'two-pointers'], + '350': ['binary-search', 'hash-table', 'sort', 'two-pointers'], + '351': ['backtracking', 'dynamic-programming'], + '352': ['binary-search-tree'], + '353': ['design', 'queue'], + '354': ['binary-search', 'dynamic-programming'], + '355': ['design', 'hash-table', 'heap'], + '356': ['hash-table', 'math'], + '357': ['backtracking', 'dynamic-programming', 'math'], + '358': ['greedy', 'hash-table', 'heap'], + '359': ['design', 'hash-table'], + '360': ['math', 'two-pointers'], + '361': ['dynamic-programming'], + '362': ['design'], + '363': ['binary-search', 'dynamic-programming', 'queue'], + '364': ['depth-first-search'], + '365': ['math'], + '366': ['depth-first-search', 'tree'], + '367': ['binary-search', 'math'], + '368': ['dynamic-programming', 'math'], + '369': ['linked-list'], + '370': ['array'], + '371': ['bit-manipulation'], + '372': ['math'], + '373': ['heap'], + '374': ['binary-search'], + '375': ['dynamic-programming'], + '376': ['dynamic-programming', 'greedy'], + '377': ['dynamic-programming'], + '378': ['binary-search', 'heap'], + '379': ['design', 'linked-list'], + '380': ['array', 'design', 'hash-table'], + '381': ['array', 'design', 'hash-table'], + '382': ['reservoir-sampling'], + '383': ['string'], + '385': ['stack', 'string'], + '387': ['hash-table', 'string'], + '389': ['bit-manipulation', 'hash-table'], + '392': ['binary-search', 'dynamic-programming', 'greedy'], + '393': ['bit-manipulation'], + '394': ['depth-first-search', 'stack'], + '396': ['math'], + '397': ['bit-manipulation', 'math'], + '398': ['reservoir-sampling'], + '399': ['graph'], + '400': ['math'], + '401': ['backtracking', 'bit-manipulation'], + '402': ['greedy', 'stack'], + '403': ['dynamic-programming'], + '404': ['tree'], + '405': ['bit-manipulation'], + '406': ['greedy'], + '407': ['breadth-first-search', 'heap'], + '408': ['string'], + '409': ['hash-table'], + '410': ['binary-search', 'dynamic-programming'], + '411': ['backtracking', 'bit-manipulation'], + '413': ['dynamic-programming', 'math'], + '414': ['array'], + '415': ['math'], + '416': ['dynamic-programming'], + '417': ['breadth-first-search', 'depth-first-search'], + '418': ['dynamic-programming'], + '421': ['bit-manipulation', 'trie'], + '423': ['math'], + '425': ['backtracking', 'trie'], + '432': ['design'], + '434': ['string'], + '435': ['greedy'], + '436': ['binary-search'], + '437': ['tree'], + '438': ['hash-table'], + '439': ['depth-first-search', 'stack'], + '441': ['binary-search', 'math'], + '442': ['array'], + '443': ['string'], + '444': ['graph', 'topological-sort'], + '445': ['linked-list'], + '446': ['dynamic-programming'], + '447': ['hash-table'], + '448': ['array'], + '449': ['tree'], + '450': ['tree'], + '451': ['hash-table', 'heap'], + '452': ['greedy'], + '453': ['math'], + '454': ['binary-search', 'hash-table'], + '455': ['greedy'], + '456': ['stack'], + '459': ['string'], + '460': ['design'], + '461': ['bit-manipulation'], + '462': ['math'], + '463': ['hash-table'], + '464': ['dynamic-programming'], + '466': ['dynamic-programming'], + '467': ['dynamic-programming'], + '468': ['string'], + '469': ['math'], + '471': ['dynamic-programming'], + '472': ['depth-first-search', 'dynamic-programming', 'trie'], + '473': ['depth-first-search'], + '474': ['dynamic-programming'], + '475': ['binary-search'], + '476': ['bit-manipulation'], + '477': ['bit-manipulation'], + '483': ['binary-search', 'math'], + '484': ['greedy'], + '485': ['array'], + '486': ['dynamic-programming'], + '487': ['two-pointers'], + '488': ['depth-first-search'], + '490': ['breadth-first-search', 'depth-first-search'], + '491': ['depth-first-search'], + '493': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree', 'binary-search-tree'], + '494': ['depth-first-search', 'dynamic-programming'], + '495': ['array'], + '496': ['stack'], + '499': ['breadth-first-search', 'depth-first-search'], + '500': ['hash-table'], + '501': ['tree'], + '502': ['greedy', 'heap'], + '503': ['stack'], + '505': ['breadth-first-search', 'depth-first-search'], + '507': ['math'], + '508': ['hash-table', 'tree'], + '513': ['breadth-first-search', 'depth-first-search', 'tree'], + '514': ['depth-first-search', 'divide-and-conquer', 'dynamic-programming'], + '515': ['breadth-first-search', 'depth-first-search', 'tree'], + '516': ['dynamic-programming'], + '517': ['dynamic-programming', 'math'], + '520': ['string'], + '521': ['string'], + '522': ['string'], + '523': ['dynamic-programming', 'math'], + '524': ['sort', 'two-pointers'], + '525': ['hash-table'], + '526': ['backtracking'], + '527': ['sort', 'string'], + '529': ['breadth-first-search', 'depth-first-search'], + '530': ['array', 'depth-first-search', 'binary-search-tree'], + '531': ['array', 'depth-first-search'], + '532': ['array', 'two-pointers'], + '533': ['array', 'depth-first-search'], + '535': ['hash-table', 'math'], + '536': ['string', 'tree'], + '537': ['math', 'string'], + '538': ['tree'], + '539': ['string'], + '541': ['string'], + '542': ['breadth-first-search', 'depth-first-search'], + '543': ['tree'], + '544': ['string'], + '545': ['tree'], + '546': ['depth-first-search', 'dynamic-programming'], + '547': ['depth-first-search', 'union-find'], + '548': ['array'], + '549': ['tree'], + '551': ['string'], + '552': ['dynamic-programming'], + '553': ['math', 'string'], + '554': ['hash-table'], + '555': ['string'], + '556': ['string'], + '557': ['string'], + '560': ['array', 'map'], + '561': ['array'], + '562': ['array'], + '563': ['tree'], + '564': ['string'], + '565': ['array'], + '566': ['array'], + '567': ['two-pointers'], + '568': ['dynamic-programming'], + '572': ['tree'], + '573': ['math'], + '575': ['hash-table'], + '576': ['depth-first-search', 'dynamic-programming'], + '581': ['array'], + '582': ['queue', 'tree'], + '583': ['string'], + '588': ['design'], + '591': ['stack', 'string'], + '592': ['math'], + '593': ['math'], + '594': ['hash-table'], + '598': ['math'], + '599': ['hash-table'], + '600': ['dynamic-programming'], + '604': ['design'], + '605': ['array'], + '606': ['string', 'tree'], + '609': ['hash-table', 'string'], + '611': ['array'], + '616': ['string'], + '617': ['tree'], + '621': ['array', 'greedy', 'queue'], + '623': ['tree'], + '624': ['array', 'hash-table'], + '625': ['math'], + '628': ['array', 'math'], + '629': ['dynamic-programming'], + '630': ['greedy'], + '631': ['design'], + '632': ['hash-table', 'string', 'two-pointers'], + '633': ['math'], + '634': ['math'], + '635': ['design', 'string'], + '636': ['stack'], + '637': ['tree'], + '638': ['depth-first-search', 'dynamic-programming'], + '639': ['dynamic-programming'], + '640': ['math'], + '642': ['design', 'trie'], + '643': ['array'], + '644': ['array', 'binary-search'], + '645': ['hash-table', 'math'], + '646': ['dynamic-programming'], + '647': ['dynamic-programming', 'string'], + '648': ['hash-table', 'trie'], + '649': ['greedy'], + '650': ['dynamic-programming'], + '651': ['dynamic-programming', 'greedy', 'math'], + '652': ['tree'], + '653': ['tree'], + '654': ['tree'], + '655': ['tree'], + '656': ['dynamic-programming'], + '657': ['string'], + '658': ['binary-search'], + '659': ['greedy', 'heap'], + '660': ['math'], + '661': ['array'], + '662': ['tree'], + '663': ['tree'], + '664': ['depth-first-search', 'dynamic-programming'], + '665': ['array'], + '666': ['tree'], + '667': ['array'], + '668': ['binary-search'], + '669': ['tree'], + '670': ['array', 'math'], + '671': ['tree'], + '672': ['math'], + '673': ['dynamic-programming'], + '674': ['array'], + '675': ['breadth-first-search'], + '676': ['hash-table', 'trie'], + '677': ['trie'], + '678': ['string'], + '679': ['depth-first-search'], + '680': ['string'], + '681': ['string'], + '682': ['stack'], + '683': ['array', 'binary-search-tree'], + '684': ['graph', 'tree', 'union-find'], + '685': ['depth-first-search', 'graph', 'tree', 'union-find'], + '686': ['string'], + '687': ['tree'], + '688': ['dynamic-programming'], + '689': ['array', 'dynamic-programming'], + '690': ['breadth-first-search', 'depth-first-search', 'hash-table'], + '691': ['backtracking', 'dynamic-programming'], + '692': ['hash-table', 'heap', 'trie'], + '693': ['bit-manipulation'], + '694': ['depth-first-search', 'hash-table'], + '695': ['array', 'depth-first-search'], + '696': ['string'], + '697': ['array'], + '698': ['dynamic-programming'], + '699': ['segment-tree', 'binary-search-tree'], + '711': ['depth-first-search', 'hash-table'], + '712': ['dynamic-programming'], + '713': ['array', 'two-pointers'], + '714': ['array', 'dynamic-programming', 'greedy'], + '715': ['array', 'segment-tree', 'binary-search-tree'], + '716': ['design'], + '717': ['array'], + '718': ['array', 'binary-search', 'dynamic-programming', 'hash-table'], + '719': ['array', 'binary-search', 'heap'], + '720': ['hash-table', 'trie'], + '721': ['depth-first-search', 'union-find'], + '722': ['string'], + '723': ['array', 'two-pointers'], + '724': ['array'], + '725': ['linked-list'], + '726': ['hash-table', 'stack'], + '727': ['dynamic-programming'], + '728': ['math'], + '729': ['array'], + '730': ['dynamic-programming', 'string'], + '731': ['array', 'binary-search-tree'], + '732': ['segment-tree', 'binary-search-tree'], + '733': ['depth-first-search'], + '734': ['hash-table'], + '735': ['stack'], + '736': ['string'], + '737': ['depth-first-search', 'union-find'], + '738': ['greedy'], + '739': ['hash-table', 'stack'], + '740': ['dynamic-programming'], + '741': ['dynamic-programming'], + '742': ['tree'], + '743': ['breadth-first-search', 'depth-first-search', 'graph', 'heap'], + '744': ['binary-search'], + '745': ['trie'], + '746': ['array', 'dynamic-programming'], + '748': ['hash-table'], + '749': ['depth-first-search'], + '750': ['dynamic-programming'] +}; + +plugin.getProblems = function(cb) { + plugin.next.getProblems(function(e, problems) { + if (e) return cb(e); + + problems.forEach(function(problem) { + var id = String(problem.id); + if (id in COMPONIES) { + problem.companies = (problem.companies || []).concat(COMPONIES[id]); + } + if (id in TAGS) { + problem.tags = (problem.tags || []).concat(TAGS[id]); + } + }); + return cb(null, problems); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/cookie.chrome.js b/lib/plugins/cookie.chrome.js new file mode 100644 index 00000000..a0a43f96 --- /dev/null +++ b/lib/plugins/cookie.chrome.js @@ -0,0 +1,190 @@ +var path = require('path'); + +var log = require('../log'); +var Plugin = require('../plugin'); +var Queue = require('../queue'); +var session = require('../session'); +var config = require('../config'); + +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md +// +var plugin = new Plugin(13, 'cookie.chrome', '2018.11.18', + 'Plugin to reuse Chrome\'s leetcode cookie.', + ['ffi:win32', 'keytar:darwin', 'ref:win32', 'ref-struct:win32', 'sqlite3']); + +plugin.help = function() { + switch (process.platform) { + case 'darwin': + break; + case 'linux': + log.warn('To complete the install: sudo apt install libsecret-tools'); + break; + case 'win32': + break; + } +}; + +var Chrome = {}; + +var ChromeMAC = { + getDBPath: function() { + return `${process.env.HOME}/Library/Application Support/Google/Chrome/${this.profile}/Cookies`; + }, + iterations: 1003, + getPassword: function(cb) { + var keytar = require('keytar'); + keytar.getPassword('Chrome Safe Storage', 'Chrome').then(cb); + } +}; + +var ChromeLinux = { + getDBPath: function() { + return `${process.env.HOME}/.config/google-chrome/${this.profile}/Cookies`; + }, + iterations: 1, + getPassword: function(cb) { + // FIXME: keytar failed to read gnome-keyring on ubuntu?? + var cmd = 'secret-tool lookup application chrome'; + var password = require('child_process').execSync(cmd).toString(); + return cb(password); + } +}; + +var ChromeWindows = { + getDBPath: function() { + return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`); + }, + getPassword: function(cb) { cb(); } +}; + +Object.setPrototypeOf(ChromeMAC, Chrome); +Object.setPrototypeOf(ChromeLinux, Chrome); +Object.setPrototypeOf(ChromeWindows, Chrome); + +Chrome.getInstance = function() { + switch (process.platform) { + case 'darwin': return ChromeMAC; + case 'linux': return ChromeLinux; + case 'win32': return ChromeWindows; + } +}; +var my = Chrome.getInstance(); + +ChromeWindows.decodeCookie = function(cookie, cb) { + var ref = require('ref'); + var ffi = require('ffi'); + var Struct = require('ref-struct'); + + var DATA_BLOB = Struct({ + cbData: ref.types.uint32, + pbData: ref.refType(ref.types.byte) + }); + var PDATA_BLOB = new ref.refType(DATA_BLOB); + var Crypto = new ffi.Library('Crypt32', { + 'CryptUnprotectData': ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]] + }); + + var inBlob = new DATA_BLOB(); + inBlob.pbData = cookie; + inBlob.cbData = cookie.length; + var outBlob = ref.alloc(DATA_BLOB); + + Crypto.CryptUnprotectData(inBlob.ref(), null, null, null, null, 0, outBlob); + var outDeref = outBlob.deref(); + var buf = ref.reinterpret(outDeref.pbData, outDeref.cbData, 0); + + return cb(null, buf.toString('utf8')); +}; + +Chrome.decodeCookie = function(cookie, cb) { + var crypto = require('crypto'); + crypto.pbkdf2(my.password, 'saltysalt', my.iterations, 16, 'sha1', function(e, key) { + if (e) return cb(e); + + var iv = new Buffer(' '.repeat(16)); + var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); + decipher.setAutoPadding(false); + + var buf = decipher.update(cookie.slice(3)); // remove prefix "v10" or "v11" + var final = decipher.final(); + final.copy(buf, buf.length - 1); + + var padding = buf[buf.length - 1]; + if (padding) buf = buf.slice(0, buf.length - padding); + + return cb(null, buf.toString('utf8')); + }); +}; + +function doDecode(key, queue, cb) { + var ctx = queue.ctx; + var cookie = ctx[key]; + if (!cookie) return cb('Not found cookie: ' + key); + + my.decodeCookie(cookie, function(e, cookie) { + ctx[key] = cookie; + return cb(); + }); +} + +Chrome.getCookies = function(cb) { + var sqlite3 = require('sqlite3'); + var db = new sqlite3.Database(my.getDBPath()); + db.on('error', cb); + var KEYS = ['csrftoken', 'LEETCODE_SESSION']; + + let host = config.sys.cookie_host; + if (!host) { + host = 'leetcode.com'; // default host key + } + + db.serialize(function() { + var cookies = {}; + var sql = 'select name, encrypted_value from cookies where host_key like "%' + host +'"'; + db.each(sql, function(e, x) { + if (e) return cb(e); + if (KEYS.indexOf(x.name) < 0) return; + cookies[x.name] = x.encrypted_value; + }); + + db.close(function() { + my.getPassword(function(password) { + my.password = password; + var q = new Queue(KEYS, cookies, doDecode); + q.run(null, cb); + }); + }); + }); +}; + +plugin.signin = function(user, cb) { + log.debug('running cookie.chrome.signin'); + log.debug('try to copy leetcode cookies from chrome ...'); + + my.profile = plugin.config.profile || 'Default'; + my.getCookies(function(e, cookies) { + if (e) { + log.error(`Failed to copy cookies from profile "${my.profile}"`); + log.error(e); + return plugin.next.signin(user, cb); + } + + log.debug('Successfully copied leetcode cookies!'); + user.sessionId = cookies.LEETCODE_SESSION; + user.sessionCSRF = cookies.csrftoken; + session.saveUser(user); + return cb(null, user); + }); +}; + +plugin.login = function(user, cb) { + log.debug('running cookie.chrome.login'); + plugin.signin(user, function(e, user) { + if (e) return cb(e); + plugin.getUser(user, cb); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/cookie.edge.js b/lib/plugins/cookie.edge.js new file mode 100644 index 00000000..28b30e5e --- /dev/null +++ b/lib/plugins/cookie.edge.js @@ -0,0 +1,190 @@ +var path = require('path'); + +var log = require('../log'); +var Plugin = require('../plugin'); +var Queue = require('../queue'); +var session = require('../session'); +var config = require('../config'); + +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.edge.md +// +var plugin = new Plugin(13, 'cookie.edge', '2018.11.18', + 'Plugin to reuse Edge\'s leetcode cookie.', + ['ffi:win32', 'keytar:darwin', 'ref:win32', 'ref-struct:win32', 'sqlite3']); + +plugin.help = function() { + switch (process.platform) { + case 'darwin': + break; + case 'linux': + log.warn('To complete the install: sudo apt install libsecret-tools'); + break; + case 'win32': + break; + } +}; + +var Chrome = {}; + +var ChromeMAC = { + getDBPath: function() { + return `${process.env.HOME}/Library/Application Support/Microsoft Edge/${this.profile}/Cookies`; + }, + iterations: 1003, + getPassword: function(cb) { + var keytar = require('keytar'); + keytar.getPassword('Microsoft Edge Safe Storage', 'Microsoft Edge').then(cb); + } +}; + +var ChromeLinux = { + getDBPath: function() { + return `${process.env.HOME}/.config/google-edge/${this.profile}/Cookies`; + }, + iterations: 1, + getPassword: function(cb) { + // FIXME: keytar failed to read gnome-keyring on ubuntu?? + var cmd = 'secret-tool lookup application edge'; + var password = require('child_process').execSync(cmd).toString(); + return cb(password); + } +}; + +var ChromeWindows = { + getDBPath: function() { + return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`); + }, + getPassword: function(cb) { cb(); } +}; + +Object.setPrototypeOf(ChromeMAC, Chrome); +Object.setPrototypeOf(ChromeLinux, Chrome); +Object.setPrototypeOf(ChromeWindows, Chrome); + +Chrome.getInstance = function() { + switch (process.platform) { + case 'darwin': return ChromeMAC; + case 'linux': return ChromeLinux; + case 'win32': return ChromeWindows; + } +}; +var my = Chrome.getInstance(); + +ChromeWindows.decodeCookie = function(cookie, cb) { + var ref = require('ref'); + var ffi = require('ffi'); + var Struct = require('ref-struct'); + + var DATA_BLOB = Struct({ + cbData: ref.types.uint32, + pbData: ref.refType(ref.types.byte) + }); + var PDATA_BLOB = new ref.refType(DATA_BLOB); + var Crypto = new ffi.Library('Crypt32', { + 'CryptUnprotectData': ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]] + }); + + var inBlob = new DATA_BLOB(); + inBlob.pbData = cookie; + inBlob.cbData = cookie.length; + var outBlob = ref.alloc(DATA_BLOB); + + Crypto.CryptUnprotectData(inBlob.ref(), null, null, null, null, 0, outBlob); + var outDeref = outBlob.deref(); + var buf = ref.reinterpret(outDeref.pbData, outDeref.cbData, 0); + + return cb(null, buf.toString('utf8')); +}; + +Chrome.decodeCookie = function(cookie, cb) { + var crypto = require('crypto'); + crypto.pbkdf2(my.password, 'saltysalt', my.iterations, 16, 'sha1', function(e, key) { + if (e) return cb(e); + + var iv = new Buffer(' '.repeat(16)); + var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); + decipher.setAutoPadding(false); + + var buf = decipher.update(cookie.slice(3)); // remove prefix "v10" or "v11" + var final = decipher.final(); + final.copy(buf, buf.length - 1); + + var padding = buf[buf.length - 1]; + if (padding) buf = buf.slice(0, buf.length - padding); + + return cb(null, buf.toString('utf8')); + }); +}; + +function doDecode(key, queue, cb) { + var ctx = queue.ctx; + var cookie = ctx[key]; + if (!cookie) return cb('Not found cookie: ' + key); + + my.decodeCookie(cookie, function(e, cookie) { + ctx[key] = cookie; + return cb(); + }); +} + +Chrome.getCookies = function(cb) { + var sqlite3 = require('sqlite3'); + var db = new sqlite3.Database(my.getDBPath()); + db.on('error', cb); + var KEYS = ['csrftoken', 'LEETCODE_SESSION']; + + let host = config.sys.cookie_host; + if (!host) { + host = 'leetcode.com'; // default host key + } + + db.serialize(function() { + var cookies = {}; + var sql = 'select name, encrypted_value from cookies where host_key like "%' + host +'"'; + db.each(sql, function(e, x) { + if (e) return cb(e); + if (KEYS.indexOf(x.name) < 0) return; + cookies[x.name] = x.encrypted_value; + }); + + db.close(function() { + my.getPassword(function(password) { + my.password = password; + var q = new Queue(KEYS, cookies, doDecode); + q.run(null, cb); + }); + }); + }); +}; + +plugin.signin = function(user, cb) { + log.debug('running cookie.edge.signin'); + log.debug('try to copy leetcode cookies from edge ...'); + + my.profile = plugin.config.profile || 'Default'; + my.getCookies(function(e, cookies) { + if (e) { + log.error(`Failed to copy cookies from profile "${my.profile}"`); + log.error(e); + return plugin.next.signin(user, cb); + } + + log.debug('Successfully copied leetcode cookies!'); + user.sessionId = cookies.LEETCODE_SESSION; + user.sessionCSRF = cookies.csrftoken; + session.saveUser(user); + return cb(null, user); + }); +}; + +plugin.login = function(user, cb) { + log.debug('running cookie.edge.login'); + plugin.signin(user, function(e, user) { + if (e) return cb(e); + plugin.getUser(user, cb); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/cookie.firefox.js b/lib/plugins/cookie.firefox.js new file mode 100644 index 00000000..f69f8699 --- /dev/null +++ b/lib/plugins/cookie.firefox.js @@ -0,0 +1,83 @@ +var log = require('../log'); +var Plugin = require('../plugin'); +var session = require('../session'); + +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.firefox.md +// +var plugin = new Plugin(13, 'cookie.firefox', '2018.11.19', + 'Plugin to reuse firefox\'s leetcode cookie.', + ['glob', 'sqlite3']); + +function getCookieFile(cb) { + var f; + switch (process.platform) { + case 'darwin': + f = process.env.HOME + '/Library/Application Support/Firefox/Profiles/*.default*/cookies.sqlite'; + break; + case 'linux': + f = process.env.HOME + '/.mozilla/firefox/*.default*/cookies.sqlite'; + break; + case 'win32': + f = (process.env.APPDATA || '') + '/Mozilla/Firefox/Profiles/*.default*/cookies.sqlite'; + break; + } + require('glob')(f, {}, cb); +} + +function getCookies(cb) { + getCookieFile(function(e, files) { + if (e || files.length === 0) return cb('Not found cookie file!'); + + var sqlite3 = require('sqlite3'); + var db = new sqlite3.Database(files[0]); + var KEYS = ['csrftoken', 'LEETCODE_SESSION']; + + db.serialize(function() { + var cookies = {}; + var sql = 'select name, value from moz_cookies where host like "%leetcode.com"'; + db.each(sql, function(e, x) { + if (e) return cb(e); + if (KEYS.indexOf(x.name) < 0) return; + cookies[x.name] = x.value; + }); + + db.close(function() { + return cb(null, cookies); + }); + }); + }); +} + +plugin.signin = function(user, cb) { + log.debug('running cookie.firefox.signin'); + log.debug('try to copy leetcode cookies from firefox ...'); + getCookies(function(e, cookies) { + if (e) { + log.error('Failed to copy cookies: ' + e); + return plugin.next.signin(user, cb); + } + + if (!cookies.LEETCODE_SESSION || !cookies.csrftoken) { + log.error('Got invalid cookies: ' + JSON.stringify(cookies)); + return plugin.next.signin(user, cb); + } + + log.debug('Successfully copied leetcode cookies!'); + user.sessionId = cookies.LEETCODE_SESSION; + user.sessionCSRF = cookies.csrftoken; + session.saveUser(user); + return cb(null, user); + }); +}; + +plugin.login = function(user, cb) { + log.debug('running cookie.firefox.login'); + plugin.signin(user, function(e, user) { + if (e) return cb(e); + plugin.getUser(user, cb); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/cpp.run.js b/lib/plugins/cpp.run.js new file mode 100644 index 00000000..6f206f8f --- /dev/null +++ b/lib/plugins/cpp.run.js @@ -0,0 +1,225 @@ +var cp = require('child_process'); +var fs = require('fs'); + +var h = require('../helper'); +var log = require('../log'); +var Plugin = require('../plugin.js'); +var session = require('../session'); + +// Please note that we DON'T want implement a lightweight judge engine +// here, thus we are NOT going to support all the problems!!! +// +// Only works for those problems could be easily tested. +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cpp.run.md +// +var plugin = new Plugin(100, 'cpp.run', '2017.07.29', + 'Plugin to run cpp code locally for debugging.'); + +var FILE_SRC = '.tmp.cpp.run.cpp'; +var FILE_EXEC = '.tmp.cpp.run.exe'; + +plugin.testProblem = function(problem, cb) { + if (!session.argv.local || h.extToLang(problem.file) !== 'cpp') + return plugin.next.testProblem(problem, cb); + + log.info('\nTesting locally ...\n'); + + // generate full cpp source code that runnable + var meta = problem.templateMeta; + + var code = fs.readFileSync(problem.file).toString(); + var re = code.match(new RegExp(' ' + meta.name + '\\((.+)\\)')); + if (!re) return cb('failed to generate runnable code!'); + + var types = re[1].split(',').map(function(x) { + var parts = x.trim().split(' '); + parts.pop(); // skip param name + return parts.join(' '); + }); + + var values = problem.testcase.split('\n').map(function(x, i) { + // TODO: handle more special types?? + // array, list, tree, etc + var t = meta.params[i].type; + if (t.indexOf('[]') >= 0 || t === 'ListNode' || t === 'TreeNode') + x = x.replace(/\[/g, '{').replace(/\]/g, '}'); + if (t === 'ListNode') x = 'make_listnode(' + x + ')'; + if (t === 'TreeNode') x = 'make_treenode(' + x + ')'; + + return x; + }); + + var data = DATA.replace('$code', code) + .replace('$method', meta.name) + .replace('$argDefs', values.map(function(x, i) { + return ' decay<' + types[i] + '>::type ' + 'p' + i + ' = ' + x + ';'; + }).join('\n')) + .replace('$args', values.map(function(x, i) { + return 'p' + i; + }).join(',')); + + fs.writeFileSync(FILE_SRC, data); + + // compile and run + var cmd = [ + 'g++', + '-std=c++11', + '-o', + FILE_EXEC, + FILE_SRC, + '&&', + './' + FILE_EXEC + ].join(' '); + cp.exec(cmd, function(e, stdout, stderr) { + if (e) { + stderr.split('\n').forEach(function(line) { + if (line.length > 0) log.error(line); + }); + } else { + stdout.split('\n').forEach(function(line) { + if (line.length > 0) log.info(line); + }); + } + }); +}; + +// FIXME: use file template!! +var DATA = ` +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201103L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +using namespace std; + +/// leetcode defined data types /// +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(NULL) {} +}; + +struct TreeNode { + int val; + TreeNode *left, *right; + TreeNode(int x) : val(x), left(NULL), right(NULL) {} +}; + +ListNode* make_listnode(const vector &v) { + ListNode head(0), *p = &head, *cur; + for (auto x: v) { cur = new ListNode(x); p->next = cur; p = cur; } + return head.next; +} + +constexpr long long null = numeric_limits::min(); + +TreeNode* make_treenode(const vector &v) { + vector cur, next; + TreeNode root(0); cur.push_back(&root); + long long i = 0, n = v.size(), x; + while (i < n) { + for (auto p: cur) { + if ((x = v[i++]) != null) { p->left = new TreeNode(x); next.push_back(p->left); } + if (i == n || p == &root) continue; + if ((x = v[i++]) != null) { p->right = new TreeNode(x); next.push_back(p->right); } + } + cur.swap(next); next.clear(); + } + return root.left; +} + +template +ostream& operator<<(ostream &os, const vector &v) { + os << "["; + for (int i = 0; i < v.size(); ++i) os << (i > 0 ? "," : "") << v[i]; + os << "]"; + return os; +} + +ostream& operator<<(ostream &os, const ListNode *p) { + vector v; + while (p) { v.push_back(p->val); p = p->next; } + return os << v; +} + +ostream& operator<<(ostream &os, const TreeNode *t) { + vector v; + queue cur, next; + if (t) cur.push(t); + + while (!cur.empty()) { + t = cur.front(); cur.pop(); + v.push_back(t ? to_string(t->val) : "null"); + if (t && (t->left || t->right)) { + next.push(t->left); + if (t->right || !cur.empty()) next.push(t->right); + } + if (cur.empty()) cur.swap(next); + } + return os << v; +} + +$code +int main() { + Solution s; +$argDefs + auto res = s.$method($args); + cout << res << endl; + return 0; +} +`; + +module.exports = plugin; diff --git a/lib/plugins/github.js b/lib/plugins/github.js new file mode 100644 index 00000000..6c94e9ec --- /dev/null +++ b/lib/plugins/github.js @@ -0,0 +1,79 @@ +var path = require('path'); +var url = require('url'); + +var h = require('../helper'); +var file = require('../file'); +var log = require('../log'); +var Plugin = require('../plugin'); + +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/github.md +// +var plugin = new Plugin(100, 'github', '2018.11.18', + 'Plugin to commit accepted code to your own github repo.', + ['github@13']); + +var ctx = {}; + +plugin.submitProblem = function(problem, cb) { + // TODO: unify error handling + if (!plugin.config.repo) + return log.error('GitHub repo not configured correctly! (plugins:github:repo)'); + if (!plugin.config.token) + return log.error('GitHub token not configured correctly! (plugins:github:token)'); + + var parts = url.parse(plugin.config.repo).pathname.split('/'); + var filename = path.basename(problem.file); + parts.push(filename); + + if (parts[0] === '') parts.shift(); + ctx.owner = parts.shift(); + ctx.repo = parts.shift(); + ctx.path = parts.join('/'); + + var GitHubApi = require('github'); + var github = new GitHubApi({host: 'api.github.com'}); + github.authenticate({type: 'token', token: plugin.config.token}); + + plugin.next.submitProblem(problem, function(_e, results) { + cb(_e, results); + if (_e || !results[0].ok) return; + + log.debug('running github.getContent: ' + filename); + github.repos.getContent(ctx, function(e, res) { + if (e && e.code !== 404) { + return log.info(' ' + h.prettyText(' ' + e.message, false)); + } + + ctx.message = 'update ' + filename; + ctx.content = new Buffer(file.data(problem.file)).toString('base64'); + + var onFileDone = function(e, res) { + if (e) + return log.info(' ' + h.prettyText(' ' + e.message, false)); + + log.debug(res.meta.status); + log.debug('updated current file version = ' + res.data.content.sha); + log.debug('updated current commit = ' + res.data.commit.sha); + log.info(' ' + h.prettyText(' Committed to ' + plugin.config.repo, true)); + }; + + if (e) { + log.debug('no previous file version found'); + + log.debug('running github.createFile'); + github.repos.createFile(ctx, onFileDone); + } else { + ctx.sha = res.data.sha; + log.debug('found previous file version = ' + ctx.sha); + + log.debug('running github.updateFile'); + github.repos.updateFile(ctx, onFileDone); + } + }); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/leetcode.cn.js b/lib/plugins/leetcode.cn.js new file mode 100644 index 00000000..f74dbbfb --- /dev/null +++ b/lib/plugins/leetcode.cn.js @@ -0,0 +1,124 @@ +'use strict' +var request = require('request'); + +var config = require('../config'); +var h = require('../helper'); +var log = require('../log'); +var Plugin = require('../plugin'); +var session = require('../session'); + +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md +// +var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25', + 'Plugin to talk with leetcode-cn APIs.'); + +plugin.init = function() { + config.app = 'leetcode.cn'; + config.sys.urls.base = 'https://leetcode-cn.com'; + config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/'; + config.sys.urls.problems = 'https://leetcode-cn.com/api/problems/$category/'; + config.sys.urls.problem = 'https://leetcode-cn.com/problems/$slug/description/'; + config.sys.urls.graphql = 'https://leetcode-cn.com/graphql'; + config.sys.urls.problem_detail = 'https://leetcode-cn.com/graphql'; + config.sys.urls.test = 'https://leetcode-cn.com/problems/$slug/interpret_solution/'; + config.sys.urls.session = 'https://leetcode-cn.com/session/'; + config.sys.urls.submit = 'https://leetcode-cn.com/problems/$slug/submit/'; + config.sys.urls.submissions = 'https://leetcode-cn.com/api/submissions/$slug'; + config.sys.urls.submission = 'https://leetcode-cn.com/submissions/detail/$id/'; + config.sys.urls.verify = 'https://leetcode-cn.com/submissions/detail/$id/check/'; + config.sys.urls.favorites = 'https://leetcode-cn.com/list/api/questions'; + config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id'; + config.sys.cookie_host = 'leetcode-cn.com'; +}; + +// FIXME: refactor those +// update options with user credentials +function signOpts(opts, user) { + opts.headers.Cookie = 'LEETCODE_SESSION=' + user.sessionId + + ';csrftoken=' + user.sessionCSRF + ';'; + opts.headers['X-CSRFToken'] = user.sessionCSRF; + opts.headers['X-Requested-With'] = 'XMLHttpRequest'; +} + +function makeOpts(url) { + const opts = {}; + opts.url = url; + opts.headers = {}; + + if (session.isLogin()) + signOpts(opts, session.getUser()); + return opts; +} + +function checkError(e, resp, expectedStatus) { + if (!e && resp && resp.statusCode !== expectedStatus) { + const code = resp.statusCode; + log.debug('http error: ' + code); + + if (code === 403 || code === 401) { + e = session.errors.EXPIRED; + } else { + e = {msg: 'http error', statusCode: code}; + } + } + return e; +} + +plugin.getProblems = function(cb) { + plugin.next.getProblems(function(e, problems) { + if (e) return cb(e); + + plugin.getProblemsTitle(function(e, titles) { + if (e) return cb(e); + + problems.forEach(function(problem) { + const title = titles[problem.id] ? titles[problem.id]: problem.name; + problem.translatedName = title; + }); + + return cb(null, problems); + }); + }); +}; + +plugin.getProblemsTitle = function(cb) { + log.debug('running leetcode.cn.getProblemNames'); + + const opts = makeOpts(config.sys.urls.graphql); + opts.headers.Origin = config.sys.urls.base; + opts.headers.Referer = 'https://leetcode-cn.com/api/problems/algorithms/'; + + opts.json = true; + opts.body = { + query: [ + 'query getQuestionTranslation($lang: String) {', + ' translations: allAppliedQuestionTranslations(lang: $lang) {', + ' title', + ' questionId', + ' }', + '}', + '' + ].join('\n'), + variables: {}, + operationName: 'getQuestionTranslation' + }; + + const spin = h.spin('Downloading questions titles'); + request.post(opts, function(e, resp, body) { + spin.stop(); + e = checkError(e, resp, 200); + if (e) return cb(e); + + const titles = []; + body.data.translations.forEach(function(x) { + titles[x.questionId] = x.title; + }); + + return cb(null, titles); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 24331ec6..8e573eb0 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -130,20 +130,21 @@ plugin.getProblem = function(problem, cb) { opts.json = true; opts.body = { query: [ - 'query getQuestionDetail($titleSlug: String!) {', + 'query questionData($titleSlug: String!) {', ' question(titleSlug: $titleSlug) {', ' content', ' stats', - ' codeDefinition', + ' codeSnippets {lang langSlug code}', ' sampleTestCase', ' enableRunCode', ' metaData', + ' topicTags {name slug translatedName },', ' translatedContent', ' }', '}' ].join('\n'), variables: {titleSlug: problem.slug}, - operationName: 'getQuestionDetail' + operationName: 'questionData' }; const spin = h.spin('Downloading ' + problem.slug); @@ -158,12 +159,18 @@ plugin.getProblem = function(problem, cb) { problem.totalAC = JSON.parse(q.stats).totalAccepted; problem.totalSubmit = JSON.parse(q.stats).totalSubmission; - let content = q.translatedContent ? q.translatedContent : q.content; - // Replace with '^' as the power operator - content = content.replace(/<\/sup>/gm, '').replace(//gm, '^'); - problem.desc = he.decode(cheerio.load(content).root().text()); + problem.desc = q.content; + problem.translatedDesc = q.translatedContent ? q.translatedContent: q.content; - problem.templates = JSON.parse(q.codeDefinition); + problem.templates = []; + for (var i = 0; i < q.codeSnippets.length; i++) { + problem.templates[i] = {}; + problem.templates[i].value = q.codeSnippets[i].langSlug; + problem.templates[i].text = q.codeSnippets[i].lang; + problem.templates[i].defaultCode = q.codeSnippets[i].code; + } + + problem.topicTags = q.topicTags; problem.testcase = q.sampleTestCase; problem.testable = q.enableRunCode; problem.templateMeta = JSON.parse(q.metaData); @@ -192,15 +199,18 @@ function runCode(opts, problem, cb) { const spin = h.spin('Sending code to judge'); request(opts, function(e, resp, body) { spin.stop(); + + const isRespStatusCodeRetry = (resp.statusCode == 429); + e = plugin.checkError(e, resp, 200); - if (e) return cb(e); + if (e && !isRespStatusCodeRetry) return cb(e); - if (body.error) { - if (!body.error.includes('too soon')) + if (isRespStatusCodeRetry || body && body.error) { + if (body && !body.error.includes('too soon')) return cb(body.error); // hit 'run code too soon' error, have to wait a bit - log.debug(body.error); + body && log.debug(body.error); // linear wait ++opts._delay; @@ -242,13 +252,17 @@ function verifyResult(task, queue, cb) { function formatResult(result) { const x = { - ok: result.run_success, - answer: result.code_answer || '', - runtime: result.status_runtime || '', - state: h.statusToName(result.status_code), - testcase: util.inspect(result.input || result.last_testcase || ''), - passed: result.total_correct || 0, - total: result.total_testcases || 0 + ok: result.run_success, + lang: result.lang, + answer: result.code_answer || '', + runtime: result.status_runtime || '', + runtime_percentile: result.runtime_percentile || '', + memory: result.status_memory || '', + memory_percentile: result.memory_percentile || '', + state: h.statusToName(result.status_code), + testcase: util.inspect(result.input || result.last_testcase || ''), + passed: result.total_correct || 0, + total: result.total_testcases || 0 }; x.error = _.chain(result) @@ -348,6 +362,9 @@ plugin.getSubmission = function(submission, cb) { }); }; + + + plugin.starProblem = function(problem, starred, cb) { log.debug('running leetcode.starProblem'); const opts = plugin.makeOpts(); @@ -404,7 +421,7 @@ plugin.getUserInfo = function(cb) { '{', ' user {', ' username', - ' isCurrentUserPremium', + // ' isCurrentUserPremium', ' }', '}' ].join('\n'), @@ -499,10 +516,10 @@ plugin.signin = function(user, cb) { plugin.getUser = function(user, cb) { plugin.getFavorites(function(e, favorites) { if (!e) { + user.name = favorites.user_name; const f = favorites.favorites.private_favorites.find(f => f.name === 'Favorite'); if (f) { user.hash = f.id_hash; - user.name = favorites.user_name; } else { log.warn('Favorite not found?'); } @@ -512,7 +529,8 @@ plugin.getUser = function(user, cb) { plugin.getUserInfo(function(e, _user) { if (!e) { - user.paid = _user.isCurrentUserPremium; + user.paid = true; + // user.paid = _user.isCurrentUserPremium; user.name = _user.username; } session.saveUser(user); diff --git a/lib/plugins/lintcode.js b/lib/plugins/lintcode.js new file mode 100644 index 00000000..073d12d5 --- /dev/null +++ b/lib/plugins/lintcode.js @@ -0,0 +1,352 @@ +var _ = require('underscore'); +var cheerio = require('cheerio'); +var request = require('request'); +var util = require('util'); + +var h = require('../helper'); +var file = require('../file'); +var config = require('../config'); +var log = require('../log'); +var Plugin = require('../plugin'); +var Queue = require('../queue'); +var session = require('../session'); + +// Still working in progress! +// +// TODO: star/submissions/submission +// FIXME: why [ERROR] Error: read ECONNRESET [0]?? +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/lintcode.md +// +const plugin = new Plugin(15, 'lintcode', '2018.11.18', + 'Plugin to talk with lintcode APIs.'); + +// FIXME: add more langs +const LANGS = [ + {value: 'cpp', text: 'C++'}, + {value: 'java', text: 'Java'}, + {value: 'python', text: 'Python'} +]; + +const LEVELS = { + 0: 'Naive', + 1: 'Easy', + 2: 'Medium', + 3: 'Hard', + 4: 'Super' +}; + +var spin; + +function signOpts(opts, user) { + opts.headers.Cookie = 'sessionid=' + user.sessionId + + ';csrftoken=' + user.sessionCSRF + ';'; + opts.headers['x-csrftoken'] = user.sessionCSRF; +} + +function makeOpts(url) { + const opts = { + url: url, + headers: {} + }; + if (session.isLogin()) + signOpts(opts, session.getUser()); + return opts; +} + +function checkError(e, resp, expectedStatus) { + if (!e && resp && resp.statusCode !== expectedStatus) { + const code = resp.statusCode; + log.debug('http error: ' + code); + + if (code === 403 || code === 401) { + e = session.errors.EXPIRED; + } else { + e = {msg: 'http error', statusCode: code}; + } + } + return e; +} + +function _split(s, delim) { + return (s || '').split(delim).map(function(x) { + return x.trim(); + }).filter(function(x) { + return x.length > 0; + }); +} + +function _strip(s) { + s = s.replace(/^
/, '').replace(/<\/code><\/pre>$/, '');
+  return util.inspect(s.trim());
+}
+
+plugin.init = function() {
+  config.app = 'lintcode';
+  config.sys.urls.base           = 'https://www.lintcode.com';
+  config.sys.urls.problems       = 'https://www.lintcode.com/api/problems/?page=$page';
+  config.sys.urls.problem        = 'https://www.lintcode.com/problem/$slug/description';
+  config.sys.urls.problem_detail = 'https://www.lintcode.com/api/problems/detail/?unique_name_or_alias=$slug&_format=detail';
+  config.sys.urls.problem_code   = 'https://www.lintcode.com/api/problems/$id/reset/?language=$lang';
+  config.sys.urls.test           = 'https://www.lintcode.com/api/submissions/';
+  config.sys.urls.test_verify    = 'https://www.lintcode.com/api/submissions/refresh/?id=$id&is_test_submission=true';
+  config.sys.urls.submit_verify  = 'https://www.lintcode.com/api/submissions/refresh/?id=$id';
+  config.sys.urls.login          = 'https://www.lintcode.com/api/accounts/signin/?next=%2F';
+};
+
+plugin.getProblems = function(cb) {
+  log.debug('running lintcode.getProblems');
+
+  var problems = [];
+  const getPage = function(page, queue, cb) {
+    plugin.getPageProblems(page, function(e, _problems, ctx) {
+      if (!e) {
+        problems = problems.concat(_problems);
+        queue.tasks = _.reject(queue.tasks, x => ctx.pages > 0 && x > ctx.pages);
+      }
+      return cb(e);
+    });
+  };
+
+  const pages = _.range(1, 100);
+  const q = new Queue(pages, {}, getPage);
+  spin = h.spin('Downloading problems');
+  q.run(null, function(e, ctx) {
+    spin.stop();
+    problems = _.sortBy(problems, x => -x.id);
+    return cb(e, problems);
+  });
+};
+
+plugin.getPageProblems = function(page, cb) {
+  log.debug('running lintcode.getPageProblems: ' + page);
+  const opts = makeOpts(config.sys.urls.problems.replace('$page', page));
+
+  spin.text = 'Downloading page ' + page;
+  request(opts, function(e, resp, body) {
+    e = checkError(e, resp, 200);
+    if (e) return cb(e);
+
+    const ctx = {};
+    const json = JSON.parse(body);
+    const problems = json.problems.map(function(p, a) {
+      const problem = {
+        id:        p.id, 
+        fid:       p.id,
+        name:      p.title,
+        slug:      p.unique_name,
+        category:  'lintcode',
+        level:     LEVELS[p.level],
+        locked:    false,
+        percent:   p.accepted_rate,
+        starred:   p.is_favorited,
+        companies: p.company_tags,
+        tags:      []
+      };
+      problem.link = config.sys.urls.problem.replace('$slug', problem.slug);
+      switch (p.user_status) {
+        case 'Accepted': problem.state = 'ac'; break;
+        case 'Failed':   problem.state = 'notac'; break;
+        default:         problem.state = 'None';
+      }
+      return problem;
+    });
+
+    ctx.count = json.count;
+    ctx.pages = json.maximum_page;
+    return cb(null, problems, ctx);
+  });
+};
+
+plugin.getProblem = function(problem, cb) {
+  log.debug('running lintcode.getProblem');
+  const link = config.sys.urls.problem_detail.replace('$slug', problem.slug);
+  const opts = makeOpts(link);
+
+  const spin = h.spin('Downloading ' + problem.slug);
+  request(opts, function(e, resp, body) {
+    spin.stop();
+    e = checkError(e, resp, 200);
+    if (e) return cb(e);
+
+    const json = JSON.parse(body);
+    problem.testcase = json.testcase_sample;
+    problem.testable = problem.testcase.length > 0;
+    problem.tags = json.tags.map(x => x.name);
+    problem.desc = cheerio.load(json.description).root().text();
+    problem.totalAC = json.total_accepted;
+    problem.totalSubmit = json.total_submissions;
+    problem.templates = [];
+
+    const getLang = function(lang, queue, cb) {
+      plugin.getProblemCode(problem, lang, function(e, code) {
+        if (!e) {
+          lang = _.clone(lang);
+          lang.defaultCode = code;
+          problem.templates.push(lang);
+        }
+        return cb(e);
+      });
+    };
+
+    const q = new Queue(LANGS, {}, getLang);
+    q.run(null, e => cb(e, problem));
+  });
+};
+
+plugin.getProblemCode = function(problem, lang, cb) {
+  log.debug('running lintcode.getProblemCode:' + lang.value);
+  const url = config.sys.urls.problem_code.replace('$id', problem.id)
+                                          .replace('$lang', lang.text.replace(/\+/g, '%2B'));
+  const opts = makeOpts(url);
+
+  const spin = h.spin('Downloading code for ' + lang.text);
+  request(opts, function(e, resp, body) {
+    spin.stop();
+    e = checkError(e, resp, 200);
+    if (e) return cb(e);
+
+    var json = JSON.parse(body);
+    return cb(null, json.code);
+  });
+};
+
+function runCode(problem, isTest, cb) {
+  const lang = _.find(LANGS, x => x.value === h.extToLang(problem.file));
+  const opts = makeOpts(config.sys.urls.test);
+  opts.headers.referer = problem.link;
+  opts.form = {
+    problem_id: problem.id,
+    code:       file.data(problem.file),
+    language:   lang.text
+  };
+  if (isTest) {
+    opts.form.input = problem.testcase;
+    opts.form.is_test_submission = true;
+  }
+
+  spin = h.spin('Sending code to judge');
+  request.post(opts, function(e, resp, body) {
+    spin.stop();
+    e = checkError(e, resp, 200);
+    if (e) return cb(e);
+
+    var json = JSON.parse(body);
+    if (!json.id) return cb('Failed to start judge!');
+
+    spin = h.spin('Waiting for judge result');
+    verifyResult(json.id, isTest, cb);
+  });
+}
+
+function verifyResult(id, isTest, cb) {
+  log.debug('running verifyResult:' + id);
+  var url = isTest ? config.sys.urls.test_verify : config.sys.urls.submit_verify;
+  var opts = makeOpts(url.replace('$id', id));
+
+  request(opts, function(e, resp, body) {
+    e = checkError(e, resp, 200);
+    if (e) return cb(e);
+
+    var result = JSON.parse(body);
+    if (result.status === 'Compiling' || result.status === 'Running')
+      return setTimeout(verifyResult, 1000, id, isTest, cb);
+
+    return cb(null, formatResult(result));
+  });
+}
+
+function formatResult(result) {
+  spin.stop();
+  var x = {
+    ok:              result.status === 'Accepted',
+    type:            'Actual',
+    state:           result.status,
+    runtime:         result.time_cost + ' ms',
+    answer:          _strip(result.output),
+    stdout:          _strip(result.stdout),
+    expected_answer: _strip(result.expected),
+    testcase:        _strip(result.input),
+    passed:          result.data_accepted_count || 0,
+    total:           result.data_total_count || 0
+  };
+
+  var error = [];
+  if (result.compile_info.length > 0)
+    error = error.concat(_split(result.compile_info, '
')); + if (result.error_message.length > 0) + error = error.concat(_split(result.error_message, '
')); + x.error = error; + + // make sure everything is ok + if (error.length > 0) x.ok = false; + if (x.passed !== x.total) x.ok = false; + + return x; +} + +plugin.testProblem = function(problem, cb) { + log.debug('running lintcode.testProblem'); + runCode(problem, true, function(e, result) { + if (e) return cb(e); + + const expected = { + ok: true, + type: 'Expected', + answer: result.expected_answer, + stdout: "''" + }; + return cb(null, [result, expected]); + }); +}; + +plugin.submitProblem = function(problem, cb) { + log.debug('running lintcode.submitProblem'); + runCode(problem, false, function(e, result) { + if (e) return cb(e); + return cb(null, [result]); + }); +}; + +plugin.getSubmissions = function(problem, cb) { + return cb('Not implemented'); +}; + +plugin.getSubmission = function(submission, cb) { + return cb('Not implemented'); +}; + +plugin.starProblem = function(problem, starred, cb) { + return cb('Not implemented'); +}; + +plugin.login = function(user, cb) { + log.debug('running lintcode.login'); + const opts = { + url: config.sys.urls.login, + headers: { + 'x-csrftoken': null + }, + form: { + username_or_email: user.login, + password: user.pass + } + }; + + const spin = h.spin('Signing in lintcode.com'); + request.post(opts, function(e, resp, body) { + spin.stop(); + if (e) return cb(e); + if (resp.statusCode !== 200) return cb('invalid password?'); + + user.sessionCSRF = h.getSetCookieValue(resp, 'csrftoken'); + user.sessionId = h.getSetCookieValue(resp, 'sessionid'); + user.name = user.login; // FIXME + + return cb(null, user); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/shell.js b/lib/plugins/shell.js new file mode 100644 index 00000000..5cdbc25c --- /dev/null +++ b/lib/plugins/shell.js @@ -0,0 +1,35 @@ +var path = require('path'); + +var h = require('../helper'); +var log = require('../log'); +var Plugin = require('../plugin'); + +var plugin = new Plugin(101, 'shell', '2019.12.02', 'Plugin to exec shell after your code accepted'); + +plugin.submitProblem = function(problem, cb) { + var filename = path.basename(problem.file); + + var shellpath = ""; + if (plugin.config.path) { + shellpath = plugin.config.path; + } + + plugin.next.submitProblem(problem, function(_e, results) { + cb(_e, results); + if (_e || !results[0].ok) return; + + if (shellpath) { + log.debug('running shell: ' + filename); + + var child_process = require('child_process'); + var stdout = child_process.execFileSync(shellpath, [filename], { + encoding: 'utf8' + }); + if (stdout) { + log.info(' ' + h.prettyText(' Shell Output: ' + stdout, true)); + } + } + }); +}; + +module.exports = plugin; \ No newline at end of file diff --git a/lib/plugins/solution.discuss.js b/lib/plugins/solution.discuss.js new file mode 100644 index 00000000..e78b90af --- /dev/null +++ b/lib/plugins/solution.discuss.js @@ -0,0 +1,100 @@ +var request = require('request'); + +var log = require('../log'); +var chalk = require('../chalk'); +var Plugin = require('../plugin'); +var session = require('../session'); + +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/solution.discuss.md +// +var plugin = new Plugin(200, 'solution.discuss', '2019.02.03', + 'Plugin to fetch most voted solution in discussions.'); + +var URL_DISCUSSES = 'https://leetcode.com/graphql'; +var URL_DISCUSS = 'https://leetcode.com/problems/$slug/discuss/$id'; + +function getSolution(problem, lang, cb) { + if (!problem) return cb(); + + if (lang === 'python3') lang = 'python'; + + var opts = { + url: URL_DISCUSSES, + json: true, + body: { + query: [ + 'query questionTopicsList($questionId: String!, $orderBy: TopicSortingOption, $skip: Int, $query: String, $first: Int!, $tags: [String!]) {', + ' questionTopicsList(questionId: $questionId, orderBy: $orderBy, skip: $skip, query: $query, first: $first, tags: $tags) {', + ' ...TopicsList', + ' }', + '}', + 'fragment TopicsList on TopicConnection {', + ' totalNum', + ' edges {', + ' node {', + ' id', + ' title', + ' post {', + ' content', + ' voteCount', + ' author {', + ' username', + ' }', + ' }', + ' }', + ' }', + '}' + ].join('\n'), + + operationName: 'questionTopicsList', + variables: JSON.stringify({ + query: '', + first: 1, + skip: 0, + orderBy: 'most_votes', + questionId: '' + problem.id, + tags: [lang] + }) + } + }; + request(opts, function(e, resp, body) { + if (e) return cb(e); + if (resp.statusCode !== 200) + return cb({msg: 'http error', statusCode: resp.statusCode}); + + const solutions = body.data.questionTopicsList.edges; + const solution = solutions.length > 0 ? solutions[0].node : null; + return cb(null, solution); + }); +} + +plugin.getProblem = function(problem, cb) { + plugin.next.getProblem(problem, function(e, problem) { + if (e || !session.argv.solution) return cb(e, problem); + + var lang = session.argv.lang; + getSolution(problem, lang, function(e, solution) { + if (e) return cb(e); + if (!solution) return log.error('Solution not found for ' + lang); + + var link = URL_DISCUSS.replace('$slug', problem.slug).replace('$id', solution.id); + var content = solution.post.content.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); + + log.info(); + log.info(solution.title); + log.info(); + log.info(chalk.underline(link)); + log.info(); + log.info('* Lang: ' + lang); + log.info('* Author: ' + solution.post.author.username); + log.info('* Votes: ' + solution.post.voteCount); + log.info(); + log.info(content); + }); + }); +}; + +module.exports = plugin; diff --git a/package.json b/package.json index a9af9f7c..6fc58a1b 100644 --- a/package.json +++ b/package.json @@ -52,13 +52,19 @@ "dependencies": { "ansi-styles": "3.2.1", "cheerio": "0.20.0", + "github": "^13.1.1", + "glob": "^7.1.6", "he": "1.2.0", + "keytar": "^5.4.0", + "md5": "^2.2.1", "mkdirp": "0.5.1", - "moment": "^2.20.1", + "moment": "^2.24.0", "nconf": "0.10.0", + "open": "^7.3.0", "ora": "3.0.0", "prompt": "1.0.0", "request": "2.88.0", + "sqlite3": "^4.1.1", "supports-color": "5.5.0", "underscore": "1.9.1", "wordwrap": "1.0.0", diff --git a/templates/codeonly.tpl b/templates/codeonly.tpl index d8baa802..7bd06d2d 100644 --- a/templates/codeonly.tpl +++ b/templates/codeonly.tpl @@ -1 +1,2 @@ +${comment.line} CreateTime: ${currentTime} ${code}