From 5a2375bea8ecd3a8c67d3356adab5f1202964503 Mon Sep 17 00:00:00 2001 From: Abhilash Gnan Date: Tue, 24 Sep 2019 19:58:18 +0200 Subject: [PATCH 1/2] basic skeleton --- lib/config.js | 1 + lib/plugins/leetcode.js | 38 +++++++++++++++++++ test/mock/weekly-contest-sample.20190926.json | 1 + test/plugins/test_leetcode.js | 18 +++++++++ 4 files changed, 58 insertions(+) create mode 100644 test/mock/weekly-contest-sample.20190926.json 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/plugins/leetcode.js b/lib/plugins/leetcode.js index 0ce3a66e..4a558a5e 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -55,6 +55,44 @@ plugin.init = function() { config.app = 'leetcode'; } +plugin.getContests = function(cb) { + log.debug('running leetcode.getContests'); + let contests = [] + // TODO + // use config.sys.urls.graphql +} + +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.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
\r\n
\r\n

\r\n Welcome to the 155th LeetCode Weekly Contest\r\n

\r\n
\r\n

\r\n \r\n  Important Note\r\n

\r\n
    \r\n
  1. \r\n The penalty time has been changed from 10 minutes to 5 minutes for each wrong submission.\r\n
  2. \r\n
  3. \r\n All submissions will be run through a plagiarism checker. Any detected plagiarism will result in immediate disqualification + 3 weeks account ban. Cheating will NOT be tolerated in any way, shape, or form. Please read the Contest FAQ for more information.\r\n
  4. \r\n
\r\n

\r\n

\r\n \r\n  Announcement\r\n

\r\n

\r\n Users must register to participate. We hope you enjoy this contest!\r\n

\r\n
\r\n \r\n \r\n \r\n
\r\n
\r\n

\r\n \r\n  Prize\r\n

\r\n
    \r\n
  • \r\n 1st\r\n \r\n 5,000 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n 2nd\r\n \r\n 2,500 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n 3rd\r\n \r\n 1,000 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n 4 - 50th\r\n \r\n 300 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n 51 - 100th\r\n \r\n 100 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n 101 - 200th\r\n \r\n 50 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n Participate\r\n \r\n 5 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\n First Time Participate\r\n \r\n 200 \"LeetCoin\"\r\n \r\n
  • \r\n
  • \r\nNEW\r\n Participate Biweekly + Weekly Contests in Same Week\r\n \r\n 100 \"LeetCoin\"\r\n \r\n
  • \r\n
\r\n
\r\n
\r\n
\r\n
\r\n

Want 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') From 901a3671206b3506a5edbfbbc7daa7a324310b07 Mon Sep 17 00:00:00 2001 From: Abhilash Gnan Date: Tue, 24 Sep 2019 21:36:16 +0200 Subject: [PATCH 2/2] basic contest feature --- lib/commands/contest.js | 56 +++++++++++++++++++++++++++++++++++++++++ lib/core.js | 8 ++++++ lib/plugins/leetcode.js | 35 ++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 lib/commands/contest.js 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/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 4a558a5e..abfb2c65 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -57,10 +57,34 @@ plugin.init = function() { plugin.getContests = function(cb) { log.debug('running leetcode.getContests'); - let contests = [] - // TODO - // use config.sys.urls.graphql -} + + 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'); @@ -75,7 +99,8 @@ plugin.getContestQuestions = function(titleSlug, cb) { const questions = json.questions .map(function(p) { return { - id: p.question_id, + id: p.id, + fid: p.question_id, name: p.title, slug: p.title_slug, credit: p.credit,