diff --git a/lib/commands/contest.js b/lib/commands/contest.js new file mode 100644 index 00000000..3769456c --- /dev/null +++ b/lib/commands/contest.js @@ -0,0 +1,56 @@ +'use strict'; +var _ = require('underscore'); + +var h = require('../helper'); +var chalk = require('../chalk'); +var icon = require('../icon'); +var log = require('../log'); +var core = require('../core'); + +const cmd = { + command: 'contest [keyword]', + aliases: ['contests'], + desc: 'Solve questions from a contest', + builder: function(yargs) { + return yargs + .positional('keyword', { + type: 'string', + default: '', + describe: 'Filter contests by keyword' + }) + .example(chalk.yellow('leetcode contest'), 'List all contests') + .example(chalk.yellow('leetcode contest weekly-contest-155'), 'List questions from a contest'); + } +}; + +cmd.handler = function(argv) { + let keyword = argv.keyword; + log.debug('argv: ' + keyword); + if (keyword.length > 0) { + // Show questions of a specific contest + core.getContestQuestions(keyword, function(e, questions) { + if (e) return log.fail(e); + + for (let q of questions) { + log.printf('%-60s (%s)', + q.name, + q.slug + ); + } + }); + } else { + // List all contests + core.getContests(function(e, contests) { + if (e) return log.fail(e); + + for (let c of contests) { + log.printf('%s (%s)', + c.title, + c.titleSlug + ); + } + }); + } +}; + +module.exports = cmd; diff --git a/lib/config.js b/lib/config.js index a427b8f3..c29586f4 100644 --- a/lib/config.js +++ b/lib/config.js @@ -33,6 +33,7 @@ const DEFAULT_CONFIG = { base: 'https://leetcode.com', graphql: 'https://leetcode.com/graphql', login: 'https://leetcode.com/accounts/login/', + contest: 'https://leetcode.com/contest/api/info/$slug', problems: 'https://leetcode.com/api/problems/$category/', problem: 'https://leetcode.com/problems/$slug/description/', test: 'https://leetcode.com/problems/$slug/interpret_solution/', diff --git a/lib/core.js b/lib/core.js index 74362f78..806d4f22 100644 --- a/lib/core.js +++ b/lib/core.js @@ -128,4 +128,12 @@ core.exportProblem = function(problem, opts) { return file.render(opts.tpl, data); }; +core.getContests = function(cb) { + core.next.getContests(cb); +}; + +core.getContestQuestions = function(titleSlug, cb) { + core.next.getContestQuestions(titleSlug, cb); +}; + module.exports = core; diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 0ce3a66e..abfb2c65 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -55,6 +55,69 @@ plugin.init = function() { config.app = 'leetcode'; } +plugin.getContests = function(cb) { + log.debug('running leetcode.getContests'); + + const opts = plugin.makeOpts(config.sys.urls.graphql); + opts.headers.Origin = config.sys.urls.base; + opts.headers.Referer = config.sys.urls.base; + opts.json = true; + opts.body = { + query: [ + '{', + ' allContests {', + ' containsPremium', + ' title', + ' titleSlug', + ' }', + '}', + ].join('\n'), + variables: {} + }; + + const spin = h.spin('Fetching contests'); + request.post(opts, function(e, resp, body) { + spin.stop(); + e = plugin.checkError(e, resp, 200); + if (e) return cb(e); + + const contests = body.data.allContests; + return cb(null, contests.reverse()); + }); +}; + +plugin.getContestQuestions = function(titleSlug, cb) { + log.debug('running leetcode.getContest'); + const opts = plugin.makeOpts(config.sys.urls.contest.replace('$slug', titleSlug)); + + // TODO add spin text + request(opts, function(e, resp, body) { + e = plugin.checkError(e, resp, 200); + if (e) return cb(e); + + const json = JSON.parse(body); + const questions = json.questions + .map(function(p) { + return { + id: p.id, + fid: p.question_id, + name: p.title, + slug: p.title_slug, + credit: p.credit, + }; + }); + + return cb(null, questions); + }); + + // spin = h.spin('Downloading problems'); + // const q = new Queue(config.sys.categories, {}, getCategory); + // q.run(null, function(e) { + // spin.stop(); + // return cb(e, problems); + // }); +} + plugin.getProblems = function(cb) { log.debug('running leetcode.getProblems'); let problems = []; diff --git a/test/mock/weekly-contest-sample.20190926.json b/test/mock/weekly-contest-sample.20190926.json new file mode 100644 index 00000000..f180d118 --- /dev/null +++ b/test/mock/weekly-contest-sample.20190926.json @@ -0,0 +1 @@ +{"contest":{"id":245,"title":"Weekly Contest 155","title_slug":"weekly-contest-155","description":"\r\n\r\n
\r\n Users must register to participate. We hope you enjoy this contest!\r\n
\r\nWant millions of LeetCode users to recognize your company? Contact us to sponsor a contest.
","duration":5400,"start_time":1569119400,"is_virtual":false,"origin_start_time":1569119400,"is_private":false,"related_contest_title":null},"questions":[{"id":974,"question_id":1306,"credit":3,"title":"Minimum Absolute Difference","title_slug":"minimum-absolute-difference"},{"id":975,"question_id":1307,"credit":4,"title":"Ugly Number III","title_slug":"ugly-number-iii"},{"id":976,"question_id":1308,"credit":5,"title":"Smallest String With Swaps","title_slug":"smallest-string-with-swaps"},{"id":977,"question_id":1309,"credit":8,"title":"Sort Items by Groups Respecting Dependencies","title_slug":"sort-items-by-groups-respecting-dependencies"}],"user_num":4979,"has_chosen_contact":false,"company":{"name":"LeetCode","description":"LeetCode's mission is to create the best code learning platform helping students and engineers to land on their dream jobs. For more information please see our Jobs page.","logo":"https://assets.leetcode.com/contest/LeetCode/company_logo"},"registered":false,"containsPremium":false} \ No newline at end of file diff --git a/test/plugins/test_leetcode.js b/test/plugins/test_leetcode.js index ef099b9b..6b9159d3 100644 --- a/test/plugins/test_leetcode.js +++ b/test/plugins/test_leetcode.js @@ -107,6 +107,24 @@ describe('plugin:leetcode', function() { }); }); // #login + describe('#getContests', function() { + // TODO + }); // #getContests + + describe('#getContestQuestions', function() { + it('should ok', function(done) { + nock('https://leetcode.com') + .get('/contest/api/info/weekly-contest-155') + .replyWithFile(200, './test/mock/weekly-contest-sample.20190926.json'); + + plugin.getContestQuestions('weekly-contest-155', function(e, questions) { + assert.equal(e, null); + assert.equal(questions.length, 4); + done(); + }); + }); + }); + describe('#getProblems', function() { it('should ok', function(done) { nock('https://leetcode.com')