From d276e3adffba1e8e8f6a16d4d51d915a4766e7e7 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 14 Apr 2018 11:15:01 +0800 Subject: [PATCH 01/21] [solution] update with latest leetcode.com change refs #9 refs #11 Signed-off-by: Eric Wang --- plugins/solution.discuss.js | 73 ++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/plugins/solution.discuss.js b/plugins/solution.discuss.js index fac9c1b..1289a13 100644 --- a/plugins/solution.discuss.js +++ b/plugins/solution.discuss.js @@ -12,33 +12,45 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/solution.discuss.md // -var plugin = new Plugin(200, 'solution.discuss', '2017.12.21', +var plugin = new Plugin(200, 'solution.discuss', '2018.04.14', 'Plugin to fetch most voted solution in discussions.'); -var URL_DISCUSSES = 'https://discuss.leetcode.com/api/category/'; -var URL_DISCUSS_TOPIC = 'https://discuss.leetcode.com/topic/'; -var URL_DISCUSS_TOPIC_API = 'https://discuss.leetcode.com/api/topic/'; - -function getSolutionDetail(solution, cb) { - if (!solution) return cb(); - - request(URL_DISCUSS_TOPIC_API + solution.slug, function(e, resp, body) { - if (e) return cb(e); - if (resp.statusCode !== 200) - return cb({msg: 'http error', statusCode: resp.statusCode}); - - var data = JSON.parse(body); - solution.title = data.titleRaw; - var $ = cheerio.load(data.posts[0].content); - solution.content = $.root().text(); - return cb(null, solution); - }); -} +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(); - request(URL_DISCUSSES + problem.discuss, function(e, resp, body) { + var opts = { + url: URL_DISCUSSES, + json: true, + qs: { + query: [ + 'query fetchTopics($questionId: Int!, $pageNo: Int!, $orderBy: String!) {', + ' questionTopics(questionId: $questionId, pageNo: $pageNo, orderBy: $orderBy) {', + ' data {', + ' id', + ' title', + ' post {', + ' content', + ' voteCount', + ' author {', + ' username', + ' }', + ' }', + ' }', + ' }', + '}' + ].join('\n'), + operationName: 'fetchTopics', + variables: JSON.stringify({ + pageNo: 1, + orderBy: 'most_votes', + questionId: problem.id + }) + } + }; + request(opts, function(e, resp, body) { if (e) return cb(e); if (resp.statusCode !== 200) return cb({msg: 'http error', statusCode: resp.statusCode}); @@ -51,16 +63,16 @@ function getSolution(problem, lang, cb) { if (lang === 'javascript') langs.push('js'); if (lang === 'python3') langs.push('python'); - var data = JSON.parse(body); - var solution = _.find(data.topics, function(x) { - var keys = x.title.toLowerCase().split(' '); + var solutions = body.data.questionTopics.data; + var solution = _.find(solutions, function(x) { + var keys = x.title.toLowerCase().split(/[^\w+]/); for (var i = 0; i < keys.length; ++i) { if (langs.indexOf(keys[i]) >= 0) return true; } return false; }); - return getSolutionDetail(solution, cb); + return cb(null, solution); }); } @@ -73,16 +85,19 @@ plugin.getProblem = function(problem, cb) { 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(URL_DISCUSS_TOPIC + solution.slug)); + log.info(chalk.underline(link)); log.info(); log.info('* Lang: ' + lang); - log.info('* Author: ' + solution.user.username); - log.info('* Votes: ' + solution.votes); + log.info('* Author: ' + solution.post.author.username); + log.info('* Votes: ' + solution.post.voteCount); log.info(); - log.info(solution.content); + log.info(content); }); }); }; From 36aa07cee70b826b248662aacfb70cab71bbcb20 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 14 May 2018 10:53:12 +0800 Subject: [PATCH 02/21] Report error if missing configurations. refs #13 Signed-off-by: Eric Wang --- plugins/cpp.lint.js | 4 ++++ plugins/github.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/plugins/cpp.lint.js b/plugins/cpp.lint.js index 5f12348..a892cd5 100644 --- a/plugins/cpp.lint.js +++ b/plugins/cpp.lint.js @@ -17,6 +17,10 @@ var DEFAULT_FLAGS = [ ]; plugin.testProblem = function(problem, cb) { + // TODO: unify error handling + if (!plugin.config.bin) + return log.error('cpplint.py not configured correctly! (plugins:cpp.lint:bin)'); + var flags = DEFAULT_FLAGS.concat(plugin.config.flags || []); var cmd = [ diff --git a/plugins/github.js b/plugins/github.js index cfbb3f2..4c00e75 100644 --- a/plugins/github.js +++ b/plugins/github.js @@ -17,6 +17,12 @@ var plugin = new Plugin(100, 'github', '2018.03.24', 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); From e2cb5ae877e37151197d95df190fd09ea5f61f9b Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Tue, 29 May 2018 14:13:03 +0800 Subject: [PATCH 03/21] [lintcode] update to latest API (WIP) refs #15 #14 Signed-off-by: Eric Wang --- plugins/lintcode.js | 237 +++++++++++++++++++++----------------------- 1 file changed, 111 insertions(+), 126 deletions(-) diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 55e3aa4..657c0af 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -6,7 +6,7 @@ var util = require('util'); var h = require('../helper'); var log = require('../log'); var Plugin = require('../plugin'); -var queue = require('../queue'); +var Queue = require('../queue'); var session = require('../session'); // Still working in progress! @@ -18,36 +18,40 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/lintcode.md // -var plugin = new Plugin(15, 'lintcode', '2017.08.04', +const plugin = new Plugin(15, 'lintcode', '2018.05.29', 'Plugin to talk with lintcode APIs.'); -var config = { - URL_BASE: 'http://www.lintcode.com/en', - URL_PROBLEMS: 'http://www.lintcode.com/en/problem?page=$page', - URL_PROBLEM: 'http://www.lintcode.com/en/problem/$slug/', - URL_PROBLEM_CODE: 'http://www.lintcode.com/en/problem/api/code/?problem_id=$id&language=$lang', - URL_TEST: 'http://www.lintcode.com/submission/api/submit/', - URL_TEST_VERIFY: 'http://www.lintcode.com/submission/api/refresh/?id=$id&waiting_time=0&is_test_submission=true', - URL_SUBMIT_VERIFY: 'http://www.lintcode.com/submission/api/refresh/?id=$id&waiting_time=0', - URL_LOGIN: 'http://www.lintcode.com/en/accounts/signin/' +const config = { + URL_PROBLEMS: 'https://www.lintcode.com/api/problems/?page=$page', + URL_PROBLEM: 'https://www.lintcode.com/problem/$slug/description', + URL_PROBLEM_DETAIL: 'https://www.lintcode.com/api/problems/detail/?unique_name_or_alias=$slug&_format=detail', + URL_PROBLEM_CODE: 'https://www.lintcode.com/api/problems/$id/reset/?language=$lang', + URL_TEST: 'https://www.lintcode.com/api/submissions/', + URL_TEST_VERIFY: 'https://www.lintcode.com/api/submissions/refresh/?id=$id&is_test_submission=true', + URL_SUBMIT_VERIFY: 'https://www.lintcode.com/api/submissions/refresh/?id=$id', + URL_LOGIN: 'https://www.lintcode.com/api/accounts/signin/?next=%2F' }; -var LANGS = [ +// FIXME: add more langs +const LANGS = [ {value: 'cpp', text: 'C++'}, {value: 'java', text: 'Java'}, {value: 'python', text: 'Python'} ]; +var spin; + function signOpts(opts, user) { opts.headers.Cookie = 'sessionid=' + user.sessionId + ';csrftoken=' + user.sessionCSRF + ';'; + opts.headers['x-csrftoken'] = user.sessionCSRF; } function makeOpts(url) { - var opts = {}; - opts.url = url; - opts.headers = {}; - + const opts = { + url: url, + headers: {} + }; if (session.isLogin()) signOpts(opts, session.getUser()); return opts; @@ -55,7 +59,7 @@ function makeOpts(url) { function checkError(e, resp, expectedStatus) { if (!e && resp && resp.statusCode !== expectedStatus) { - var code = resp.statusCode; + const code = resp.statusCode; log.debug('http error: ' + code); if (code === 403 || code === 401) { @@ -84,126 +88,111 @@ plugin.getProblems = function(cb) { log.debug('running lintcode.getProblems'); var problems = []; - var doTask = function(page, taskDone) { - plugin.getPageProblems(page, function(e, _problems) { - if (!e) problems = problems.concat(_problems); - return taskDone(e); + 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); }); }; - // FIXME: remove this hardcoded range! - var pages = [0, 1, 2, 3, 4]; - queue.run(pages, doTask, function(e) { - problems = _.sortBy(problems, function(x) { - return -x.id; - }); + 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); - var opts = makeOpts(config.URL_PROBLEMS.replace('$page', page)); + const opts = makeOpts(config.URL_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); - var $ = cheerio.load(body); - var problems = $('div[id=problem_list_pagination] a').map(function(i, a) { - var problem = { - locked: false, + 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', - state: 'None', - starred: false, - companies: [], + level: h.levelToName(p.level), + locked: false, + percent: p.accepted_rate, + starred: p.is_favorited, + companies: p.company_tags, tags: [] }; - problem.slug = $(a).attr('href').split('/').pop(); problem.link = config.URL_PROBLEM.replace('$slug', problem.slug); - - $(a).children('span').each(function(i, span) { - var text = $(span).text().trim(); - var type = _split($(span).attr('class'), ' '); - type = type.concat(_split($(span).find('i').attr('class'), ' ')); - - if (type.indexOf('title') >= 0) { - problem.id = Number(text.split('.')[0]); - problem.name = text.split('.')[1].trim(); - } else if (type.indexOf('difficulty') >= 0) problem.level = text; - else if (type.indexOf('rate') >= 0) problem.percent = parseInt(text, 10); - else if (type.indexOf('fa-star') >= 0) problem.starred = true; - else if (type.indexOf('fa-check') >= 0) problem.state = 'ac'; - else if (type.indexOf('fa-minus') >= 0) problem.state = 'notac'; - else if (type.indexOf('fa-briefcase') >= 0) problem.companies = _split($(span).attr('title'), ','); - }); - + switch (p.user_status) { + case 'Accepted': problem.state = 'ac'; break; + case 'Failed': problem.state = 'notac'; break; + default: problem.state = 'None'; + } return problem; - }).get(); + }); - return cb(null, problems); + ctx.count = json.count; + ctx.pages = json.maximum_page; + return cb(null, problems, ctx); }); }; plugin.getProblem = function(problem, cb) { log.debug('running lintcode.getProblem'); - var opts = makeOpts(problem.link); + const link = config.URL_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); - var $ = cheerio.load(body); - problem.testcase = $('textarea[id=input-testcase]').text(); + const json = JSON.parse(body); + problem.testcase = json.testcase_sample; problem.testable = problem.testcase.length > 0; - - var lines = []; - $('div[id=description] > div').each(function(i, div) { - if (i === 0) { - div = $(div).find('div')[0]; - lines.push($(div).text().trim()); - return; - } - - var text = $(div).text().trim(); - var type = $(div).find('b').text().trim(); - - if (type === 'Tags') { - problem.tags = _split(text, '\n'); - problem.tags.shift(); - } else if (type === 'Related Problems') return; - else lines.push(text); - }); - problem.desc = lines.join('\n').replace(/\n{2,}/g, '\n'); - problem.totalAC = ''; - problem.totalSubmit = ''; + 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 = []; - var doTask = function(lang, taskDone) { + const getLang = function(lang, queue, cb) { plugin.getProblemCode(problem, lang, function(e, code) { - if (e) return taskDone(e); - - lang = _.clone(lang); - lang.defaultCode = code; - problem.templates.push(lang); - return taskDone(); + if (!e) { + lang = _.clone(lang); + lang.defaultCode = code; + problem.templates.push(lang); + } + return cb(e); }); }; - queue.run(LANGS, doTask, function(e) { - return cb(e, problem); - }); + 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); - var url = config.URL_PROBLEM_CODE - .replace('$id', problem.id) - .replace('$lang', lang.text.replace(/\+/g, '%2b')); - var opts = makeOpts(url); + const url = config.URL_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); @@ -213,29 +202,29 @@ plugin.getProblemCode = function(problem, lang, cb) { }; function runCode(problem, isTest, cb) { - var lang = _.find(LANGS, function(x) { - return x.value === h.extToLang(problem.file); - }); - - var opts = makeOpts(config.URL_TEST); + const lang = _.find(LANGS, x => x.value === h.extToLang(problem.file)); + const opts = makeOpts(config.URL_TEST); + opts.headers.referer = problem.link; opts.form = { problem_id: problem.id, code: h.getFileData(problem.file), - language: lang.text, - csrfmiddlewaretoken: session.getUser().sessionCSRF + 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 || !json.success) return cb(json.message); + if (!json.id) return cb('Failed to start judge!'); + spin = h.spin('Waiting for judge result'); verifyResult(json.id, isTest, cb); }); } @@ -258,6 +247,7 @@ function verifyResult(id, isTest, cb) { } function formatResult(result) { + spin.stop(); var x = { ok: result.status === 'Accepted', type: 'Actual', @@ -290,7 +280,7 @@ plugin.testProblem = function(problem, cb) { runCode(problem, true, function(e, result) { if (e) return cb(e); - var expected = { + const expected = { ok: true, type: 'Expected', answer: result.expected_answer, @@ -322,33 +312,28 @@ plugin.starProblem = function(problem, starred, cb) { plugin.login = function(user, cb) { log.debug('running lintcode.login'); - request(config.URL_LOGIN, function(e, resp, body) { - e = checkError(e, resp, 200); - if (e) return cb(e); + const opts = { + url: config.URL_LOGIN, + headers: { + 'x-csrftoken': null + }, + form: { + username_or_email: user.login, + password: user.pass + } + }; - user.loginCSRF = h.getSetCookieValue(resp, 'csrftoken'); - - var opts = { - url: config.URL_LOGIN, - headers: { - Cookie: 'csrftoken=' + user.loginCSRF + ';' - }, - form: { - csrfmiddlewaretoken: user.loginCSRF, - username_or_email: user.login, - password: user.pass - } - }; - request.post(opts, function(e, resp, body) { - if (e) return cb(e); - if (resp.statusCode !== 302) return cb('invalid password?'); + 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 + user.sessionCSRF = h.getSetCookieValue(resp, 'csrftoken'); + user.sessionId = h.getSetCookieValue(resp, 'sessionid'); + user.name = user.login; // FIXME - return cb(null, user); - }); + return cb(null, user); }); }; From a6b1a574fd65dd3001ecea1191d03f7529e385c2 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Tue, 29 May 2018 14:22:15 +0800 Subject: [PATCH 04/21] [lintcode] fixes missing level names Signed-off-by: Eric Wang --- plugins/lintcode.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 657c0af..449fbfb 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -39,6 +39,14 @@ const LANGS = [ {value: 'python', text: 'Python'} ]; +const LEVELS = { + 0: 'Naive', + 1: 'Easy', + 2: 'Medium', + 3: 'Hard', + 4: 'Super' +}; + var spin; function signOpts(opts, user) { @@ -126,7 +134,7 @@ plugin.getPageProblems = function(page, cb) { name: p.title, slug: p.unique_name, category: 'lintcode', - level: h.levelToName(p.level), + level: LEVELS[p.level], locked: false, percent: p.accepted_rate, starred: p.is_favorited, From 52b9353b0829bf91df75323376b2d8b5b898379c Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Tue, 29 May 2018 14:41:20 +0800 Subject: [PATCH 05/21] [WIP] talk to leetcode-cn.com Signed-off-by: Eric Wang --- plugins/leetcode.cn.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 plugins/leetcode.cn.js diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js new file mode 100644 index 0000000..32686ac --- /dev/null +++ b/plugins/leetcode.cn.js @@ -0,0 +1,28 @@ +var Plugin = require('../plugin'); + +// +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md +// +var plugin = new Plugin(15, 'leetcode.cn', '2018.05.29', + 'Plugin to talk with leetcode-cn APIs.'); + +plugin.init = function() { + const config = require('../config'); + 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.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'; +}; + +module.exports = plugin; From 18605dd77b1f49e8dbcb2f4f984226d2b8fca395 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 30 May 2018 12:00:28 +0800 Subject: [PATCH 06/21] Refactor to use consistent config.sys.urls Signed-off-by: Eric Wang --- plugins/leetcode.cn.js | 4 ++-- plugins/lintcode.js | 41 +++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index 32686ac..30c7167 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -5,7 +5,7 @@ var Plugin = require('../plugin'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md // -var plugin = new Plugin(15, 'leetcode.cn', '2018.05.29', +var plugin = new Plugin(15, 'leetcode.cn', '2018.05.30', 'Plugin to talk with leetcode-cn APIs.'); plugin.init = function() { @@ -14,7 +14,7 @@ plugin.init = function() { 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.problem_detail = 'https://leetcode-cn.com/graphql'; + config.sys.urls.graphql = '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/'; diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 449fbfb..7f0850d 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -4,6 +4,7 @@ var request = require('request'); var util = require('util'); var h = require('../helper'); +var config = require('../config'); var log = require('../log'); var Plugin = require('../plugin'); var Queue = require('../queue'); @@ -18,20 +19,9 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/lintcode.md // -const plugin = new Plugin(15, 'lintcode', '2018.05.29', +const plugin = new Plugin(15, 'lintcode', '2018.05.30', 'Plugin to talk with lintcode APIs.'); -const config = { - URL_PROBLEMS: 'https://www.lintcode.com/api/problems/?page=$page', - URL_PROBLEM: 'https://www.lintcode.com/problem/$slug/description', - URL_PROBLEM_DETAIL: 'https://www.lintcode.com/api/problems/detail/?unique_name_or_alias=$slug&_format=detail', - URL_PROBLEM_CODE: 'https://www.lintcode.com/api/problems/$id/reset/?language=$lang', - URL_TEST: 'https://www.lintcode.com/api/submissions/', - URL_TEST_VERIFY: 'https://www.lintcode.com/api/submissions/refresh/?id=$id&is_test_submission=true', - URL_SUBMIT_VERIFY: 'https://www.lintcode.com/api/submissions/refresh/?id=$id', - URL_LOGIN: 'https://www.lintcode.com/api/accounts/signin/?next=%2F' -}; - // FIXME: add more langs const LANGS = [ {value: 'cpp', text: 'C++'}, @@ -92,6 +82,17 @@ function _strip(s) { return util.inspect(s.trim()); } +plugin.init = function() { + 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'); @@ -118,7 +119,7 @@ plugin.getProblems = function(cb) { plugin.getPageProblems = function(page, cb) { log.debug('running lintcode.getPageProblems: ' + page); - const opts = makeOpts(config.URL_PROBLEMS.replace('$page', page)); + const opts = makeOpts(config.sys.urls.problems.replace('$page', page)); spin.text = 'Downloading page ' + page; request(opts, function(e, resp, body) { @@ -141,7 +142,7 @@ plugin.getPageProblems = function(page, cb) { companies: p.company_tags, tags: [] }; - problem.link = config.URL_PROBLEM.replace('$slug', problem.slug); + 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; @@ -158,7 +159,7 @@ plugin.getPageProblems = function(page, cb) { plugin.getProblem = function(problem, cb) { log.debug('running lintcode.getProblem'); - const link = config.URL_PROBLEM_DETAIL.replace('$slug', problem.slug); + const link = config.sys.urls.problem_detail.replace('$slug', problem.slug); const opts = makeOpts(link); const spin = h.spin('Downloading ' + problem.slug); @@ -194,8 +195,8 @@ plugin.getProblem = function(problem, cb) { plugin.getProblemCode = function(problem, lang, cb) { log.debug('running lintcode.getProblemCode:' + lang.value); - const url = config.URL_PROBLEM_CODE.replace('$id', problem.id) - .replace('$lang', lang.text.replace(/\+/g, '%2B')); + 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); @@ -211,7 +212,7 @@ plugin.getProblemCode = function(problem, lang, cb) { function runCode(problem, isTest, cb) { const lang = _.find(LANGS, x => x.value === h.extToLang(problem.file)); - const opts = makeOpts(config.URL_TEST); + const opts = makeOpts(config.sys.urls.test); opts.headers.referer = problem.link; opts.form = { problem_id: problem.id, @@ -239,7 +240,7 @@ function runCode(problem, isTest, cb) { function verifyResult(id, isTest, cb) { log.debug('running verifyResult:' + id); - var url = isTest ? config.URL_TEST_VERIFY : config.URL_SUBMIT_VERIFY; + 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) { @@ -321,7 +322,7 @@ plugin.starProblem = function(problem, starred, cb) { plugin.login = function(user, cb) { log.debug('running lintcode.login'); const opts = { - url: config.URL_LOGIN, + url: config.sys.urls.login, headers: { 'x-csrftoken': null }, From 78232d3af28e0374aca4db887ac5869e5503d9d6 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 30 May 2018 14:18:22 +0800 Subject: [PATCH 07/21] [cookie.chrome] support profile config. refs #7 Signed-off-by: Eric Wang --- docs/cookie.chrome.md | 22 ++++++++++++++++++++++ plugins/cookie.chrome.js | 21 +++++++++++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/cookie.chrome.md b/docs/cookie.chrome.md index a4f7593..c5a9dbc 100644 --- a/docs/cookie.chrome.md +++ b/docs/cookie.chrome.md @@ -26,6 +26,28 @@ Make sure build environment is ready before installing plugin: $ npm install -g windows-build-tools $ npm config set msvs_version 2015 -g +## Config + +* `profile`: chrome profile in use, default value is "Default". + +*Set Config* + + $ leetcode config plugins:cookie.chrome:profile "Your Profile" + +*Unset Config* + + $ leetcode config -d plugins:cookie.chrome + +*Example* + + { + "plugins": { + "cookie.chrome": { + "profile": "Profile 2" + } + } + } + ## Usage If enabled, the login will try to reuse existing chrome cookies. You can verify it by printing debug output as below. diff --git a/plugins/cookie.chrome.js b/plugins/cookie.chrome.js index c5ac5b6..13f28f4 100644 --- a/plugins/cookie.chrome.js +++ b/plugins/cookie.chrome.js @@ -9,7 +9,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md // -var plugin = new Plugin(13, 'cookie.chrome', '2017.12.23', +var plugin = new Plugin(13, 'cookie.chrome', '2018.05.30', 'Plugin to reuse Chrome\'s leetcode cookie.', ['ffi:win32', 'keytar:darwin', 'ref:win32', 'ref-struct:win32', 'sqlite3']); @@ -28,7 +28,9 @@ plugin.help = function() { var Chrome = {}; var ChromeMAC = { - db: process.env.HOME + '/Library/Application Support/Google/Chrome/Default/Cookies', + getDBPath: function() { + return `${process.env.HOME}/Library/Application Support/Google/Chrome/${this.profile}/Cookies`; + }, iterations: 1003, getPassword: function(cb) { var keytar = require('keytar'); @@ -37,7 +39,9 @@ var ChromeMAC = { }; var ChromeLinux = { - db: process.env.HOME + '/.config/google-chrome/Default/Cookies', + 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?? @@ -48,7 +52,9 @@ var ChromeLinux = { }; var ChromeWindows = { - db: path.resolve(process.env.APPDATA || '', '../Local/Google/Chrome/User Data/Default/Cookies'), + getDBPath: function() { + return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`); + }, getPassword: function(cb) { cb(); } }; @@ -124,7 +130,7 @@ function doDecode(key, queue, cb) { Chrome.getCookies = function(cb) { var sqlite3 = require('sqlite3'); - var db = new sqlite3.Database(my.db); + var db = new sqlite3.Database(my.getDBPath()); var KEYS = ['csrftoken', 'LEETCODE_SESSION']; db.serialize(function() { @@ -149,9 +155,12 @@ Chrome.getCookies = function(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: ' + e); + log.error(`Failed to copy cookies from profile "${my.profile}"`); + log.error(e); return plugin.next.signin(user, cb); } From 7456f72d0a7c2f3e815462e9be7d4d1c9e0108e4 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 30 May 2018 14:33:05 +0800 Subject: [PATCH 08/21] [leetcode.cn] update docs. Signed-off-by: Eric Wang --- README.md | 1 + docs/leetcode.cn.md | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 docs/leetcode.cn.md diff --git a/README.md b/README.md index 41fb9c1..ecda979 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ |[cpp.lint](/docs/cpp.lint.md)|C++ code syntax check|`test`| |[cpp.run](/docs/cpp.run.md)|Test C++ code locally|`test`| |[github](/docs/github.md)|Commit accpeted code to GitHub|`submit`| +|[leetcode.cn](/docs/leetcode.cn.md)|Fight questions from leetcode-cn.com|| |[lintcode](/docs/lintcode.md)|Fight questions from lintcode.com|`list` `show` `test` `submit` `user`| |[solution.discuss](/docs/solution.discuss.md)|Fetch top voted solution|`show`| diff --git a/docs/leetcode.cn.md b/docs/leetcode.cn.md new file mode 100644 index 0000000..3a65ddd --- /dev/null +++ b/docs/leetcode.cn.md @@ -0,0 +1,7 @@ +# leetcode.cn + +Run everything against "leetcode-cn.com" instead of "leetcode.com". + +## Requirement + +You need a valid account on leetcode-cn.com since it uses diffrent user database from leetcode.com. From 678ca1e60a9a201fa0aa46cf0441a188c8a22c45 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Thu, 31 May 2018 12:07:41 +0800 Subject: [PATCH 09/21] [lintcode] tiny fix of base url. Signed-off-by: Eric Wang --- plugins/lintcode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 7f0850d..5e92278 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -83,6 +83,7 @@ function _strip(s) { } plugin.init = function() { + 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'; From 80ac93367cd2030df0715125ef9f56f4b6d84d36 Mon Sep 17 00:00:00 2001 From: Eric Liang Date: Thu, 14 Jun 2018 11:46:18 -0700 Subject: [PATCH 10/21] Add binary-search-tree tag In Company plugin, binary search tree tag is missing. --- plugins/company.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/company.js b/plugins/company.js index 711d8a6..3884ba0 100644 --- a/plugins/company.js +++ b/plugins/company.js @@ -726,6 +726,7 @@ var TAGS = { '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'], @@ -817,7 +818,7 @@ var TAGS = { '312': ['divide-and-conquer', 'dynamic-programming'], '313': ['heap', 'math'], '314': ['hash-table'], - '315': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree'], + '315': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree', 'binary-search-tree'], '316': ['greedy', 'stack'], '317': ['breadth-first-search'], '318': ['bit-manipulation'], @@ -829,7 +830,7 @@ var TAGS = { '324': ['sort'], '325': ['hash-table'], '326': ['math'], - '327': ['divide-and-conquer'], + '327': ['divide-and-conquer', 'binary-search-tree'], '328': ['linked-list'], '329': ['depth-first-search', 'topological-sort'], '330': ['greedy'], @@ -853,6 +854,7 @@ var TAGS = { '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'], @@ -963,7 +965,7 @@ var TAGS = { '488': ['depth-first-search'], '490': ['breadth-first-search', 'depth-first-search'], '491': ['depth-first-search'], - '493': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree'], + '493': ['binary-indexed-tree', 'divide-and-conquer', 'segment-tree', 'binary-search-tree'], '494': ['depth-first-search', 'dynamic-programming'], '495': ['array'], '496': ['stack'], @@ -989,6 +991,7 @@ var TAGS = { '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'], @@ -1102,7 +1105,7 @@ var TAGS = { '680': ['string'], '681': ['string'], '682': ['stack'], - '683': ['array'], + '683': ['array', 'binary-search-tree'], '684': ['graph', 'tree', 'union-find'], '685': ['depth-first-search', 'graph', 'tree', 'union-find'], '686': ['string'], @@ -1118,12 +1121,12 @@ var TAGS = { '696': ['string'], '697': ['array'], '698': ['dynamic-programming'], - '699': ['segment-tree'], + '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'], + '715': ['array', 'segment-tree', 'binary-search-tree'], '716': ['design'], '717': ['array'], '718': ['array', 'binary-search', 'dynamic-programming', 'hash-table'], @@ -1139,8 +1142,8 @@ var TAGS = { '728': ['math'], '729': ['array'], '730': ['dynamic-programming', 'string'], - '731': ['array'], - '732': ['segment-tree'], + '731': ['array', 'binary-search-tree'], + '732': ['segment-tree', 'binary-search-tree'], '733': ['depth-first-search'], '734': ['hash-table'], '735': ['stack'], From babbdf608450da7db38fcbaca1b6baf1a2555909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=A9=E5=AD=90?= Date: Thu, 9 Aug 2018 10:18:51 +0800 Subject: [PATCH 11/21] Fix leetcode-cn urls It seems the leetcode-cn plugin need one more url replaced for working well. That is config.sys.urls.problem_detail --- plugins/leetcode.cn.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index 30c7167..8db8dfc 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -15,6 +15,7 @@ plugin.init = function() { 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.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/'; From 6ef569dcf78d4eb1375a40b65bb4f14494d19954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=A9=E5=AD=90?= Date: Thu, 9 Aug 2018 10:22:04 +0800 Subject: [PATCH 12/21] Fix url Fix url for leetcode-cn --- plugins/leetcode.cn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index 8db8dfc..53d79ce 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -15,7 +15,7 @@ plugin.init = function() { 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.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/'; From 5ef609e75e3f19451d83ccd61e70b669d972e640 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 28 Oct 2018 11:38:09 +0800 Subject: [PATCH 13/21] move plugins' specific data into separate dirs. * see https://github.com/skygragon/leetcode-cli/commit/b72d4d69fe6b3279d3f40f00bb836696139a2fcc Signed-off-by: Eric Wang --- plugins/leetcode.cn.js | 3 ++- plugins/lintcode.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index 53d79ce..9489d61 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -5,11 +5,12 @@ var Plugin = require('../plugin'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md // -var plugin = new Plugin(15, 'leetcode.cn', '2018.05.30', +var plugin = new Plugin(15, 'leetcode.cn', '2018.10.28', 'Plugin to talk with leetcode-cn APIs.'); plugin.init = function() { const config = require('../config'); + 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/'; diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 5e92278..89be320 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -19,7 +19,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/lintcode.md // -const plugin = new Plugin(15, 'lintcode', '2018.05.30', +const plugin = new Plugin(15, 'lintcode', '2018.10.28', 'Plugin to talk with lintcode APIs.'); // FIXME: add more langs @@ -83,6 +83,7 @@ function _strip(s) { } 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'; From e8fda163ca9770ab0c7f0981dd634b7e5f10f20f Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 10:51:26 +0800 Subject: [PATCH 14/21] [refactor] keep pace with leetcode-cli 2.5.4 Signed-off-by: Eric Wang --- plugins/github.js | 3 ++- plugins/lintcode.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/github.js b/plugins/github.js index 4c00e75..b0f8241 100644 --- a/plugins/github.js +++ b/plugins/github.js @@ -2,6 +2,7 @@ var path = require('path'); var url = require('url'); var h = require('../helper'); +var file = require('../file'); var log = require('../log'); var Plugin = require('../plugin'); @@ -47,7 +48,7 @@ plugin.submitProblem = function(problem, cb) { } ctx.message = 'update ' + filename; - ctx.content = new Buffer(h.getFileData(problem.file)).toString('base64'); + ctx.content = new Buffer(file.data(problem.file)).toString('base64'); var onFileDone = function(e, res) { if (e) diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 89be320..88c7e3c 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -4,6 +4,7 @@ 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'); @@ -218,7 +219,7 @@ function runCode(problem, isTest, cb) { opts.headers.referer = problem.link; opts.form = { problem_id: problem.id, - code: h.getFileData(problem.file), + code: file.data(problem.file), language: lang.text }; if (isTest) { From 23f7fdba0d37b1997a2ecef2c636968cb75c77fe Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 18 Nov 2018 23:41:14 +0800 Subject: [PATCH 15/21] tiny update. Signed-off-by: Eric Wang --- plugins/cookie.chrome.js | 5 +++-- plugins/github.js | 2 +- plugins/leetcode.cn.js | 4 ++-- plugins/lintcode.js | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/cookie.chrome.js b/plugins/cookie.chrome.js index 13f28f4..0291144 100644 --- a/plugins/cookie.chrome.js +++ b/plugins/cookie.chrome.js @@ -9,7 +9,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md // -var plugin = new Plugin(13, 'cookie.chrome', '2018.05.30', +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']); @@ -18,7 +18,7 @@ plugin.help = function() { case 'darwin': break; case 'linux': - log.info('To complete the install: sudo apt install libsecret-tools'); + log.warn('To complete the install: sudo apt install libsecret-tools'); break; case 'win32': break; @@ -131,6 +131,7 @@ function doDecode(key, queue, 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']; db.serialize(function() { diff --git a/plugins/github.js b/plugins/github.js index b0f8241..6c94e9e 100644 --- a/plugins/github.js +++ b/plugins/github.js @@ -11,7 +11,7 @@ var Plugin = require('../plugin'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/github.md // -var plugin = new Plugin(100, 'github', '2018.03.24', +var plugin = new Plugin(100, 'github', '2018.11.18', 'Plugin to commit accepted code to your own github repo.', ['github@13']); diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index 9489d61..c9b1aef 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -1,3 +1,4 @@ +var config = require('../config'); var Plugin = require('../plugin'); // @@ -5,11 +6,10 @@ var Plugin = require('../plugin'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/leetcode.cn.md // -var plugin = new Plugin(15, 'leetcode.cn', '2018.10.28', +var plugin = new Plugin(15, 'leetcode.cn', '2018.11.18', 'Plugin to talk with leetcode-cn APIs.'); plugin.init = function() { - const config = require('../config'); config.app = 'leetcode.cn'; config.sys.urls.base = 'https://leetcode-cn.com'; config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/'; diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 88c7e3c..18b5c9d 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -20,7 +20,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/lintcode.md // -const plugin = new Plugin(15, 'lintcode', '2018.10.28', +const plugin = new Plugin(15, 'lintcode', '2018.11.18', 'Plugin to talk with lintcode APIs.'); // FIXME: add more langs From aeb680a7d89ce74a039ce89cbaa3f9ff2ee4fbb7 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 19 Nov 2018 14:00:28 +0800 Subject: [PATCH 16/21] [cookie.firefox] fail on invalid cookies. Signed-off-by: Eric Wang --- plugins/cookie.firefox.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/cookie.firefox.js b/plugins/cookie.firefox.js index 45d8645..de2db29 100644 --- a/plugins/cookie.firefox.js +++ b/plugins/cookie.firefox.js @@ -6,7 +6,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.firefox.md // -var plugin = new Plugin(13, 'cookie.firefox', '2017.12.28', +var plugin = new Plugin(13, 'cookie.firefox', '2018.11.19', 'Plugin to reuse firefox\'s leetcode cookie.', ['glob', 'sqlite3']); @@ -55,7 +55,12 @@ plugin.signin = function(user, cb) { log.debug('try to copy leetcode cookies from firefox ...'); getCookies(function(e, cookies) { if (e) { - log.error('failed to copy cookies: ' + 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); } From 65cbe605aa704394dc52c385c166b6e7eadb1057 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 22:27:40 +0800 Subject: [PATCH 17/21] fixes eslint errors. Signed-off-by: Eric Wang --- .eslintrc.js | 29 ++++++++++++++++++++++++----- package.json | 4 ++-- plugins/company.js | 4 ++++ plugins/cookie.chrome.js | 6 +++--- plugins/cookie.firefox.js | 2 +- plugins/lintcode.js | 10 +++++----- plugins/solution.discuss.js | 6 +++--- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f274110..0c40bef 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,17 +1,36 @@ module.exports = { "env": { "browser": false, - "node": true, - "es6": true + "es6": true, + "mocha": true, + "node": true }, - "extends": "google", + "extends": [ + "google", + "eslint:recommended" + ], "rules": { + "block-spacing": [2, "always"], + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "camelcase": [2, {properties: "never"}], "comma-dangle": 0, "curly": 0, "key-spacing": [2, {align: "value"}], - "max-len": [1, 120], + "max-len": [1, 150], + "no-console": 1, + "no-empty": [2, { "allowEmptyCatch": true }], + "no-eval": 1, // we use it on purpose + "no-loop-func": 1, + "no-multi-spaces": 0, + "no-proto": 1, + "no-unused-expressions": 1, + "no-unused-vars": 1, "no-var": 0, + "no-warning-comments": 0, + "prefer-rest-params": 0, + "prefer-spread": 0, + "quote-props": 1, "quotes": [2, "single", {avoidEscape: true}], - "require-jsdoc": 0 + "require-jsdoc": 0, } }; diff --git a/package.json b/package.json index 99fca68..4ea4b95 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/skygragon/leetcode-cli-plugins#readme", "devDependencies": { - "eslint": "^4.4.1", - "eslint-config-google": "^0.9.1" + "eslint": "5.9.0", + "eslint-config-google": "0.11.0" } } diff --git a/plugins/company.js b/plugins/company.js index 3884ba0..ac48974 100644 --- a/plugins/company.js +++ b/plugins/company.js @@ -21,6 +21,7 @@ var COMPONIES = { '5': ['amazon', 'bloomberg', 'microsoft'], '7': ['apple', 'bloomberg'], '8': ['amazon', 'bloomberg', 'microsoft', 'uber'], + '10': ['airbnb', 'facebook', 'google', 'twitter', 'uber'], '11': ['bloomberg'], '12': ['twitter'], @@ -84,6 +85,7 @@ var COMPONIES = { '94': ['microsoft'], '96': ['snapchat'], '98': ['amazon', 'bloomberg', 'facebook', 'microsoft'], + '100': ['bloomberg'], '101': ['bloomberg', 'linkedin', 'microsoft'], '102': ['amazon', 'apple', 'bloomberg', 'facebook', 'linkedin', 'microsoft'], @@ -532,6 +534,7 @@ var TAGS = { '7': ['math'], '8': ['math', 'string'], '9': ['math'], + '10': ['backtracking', 'dynamic-programming', 'string'], '11': ['array', 'two-pointers'], '12': ['math', 'string'], @@ -622,6 +625,7 @@ var TAGS = { '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'], diff --git a/plugins/cookie.chrome.js b/plugins/cookie.chrome.js index 0291144..e2d4278 100644 --- a/plugins/cookie.chrome.js +++ b/plugins/cookie.chrome.js @@ -28,7 +28,7 @@ plugin.help = function() { var Chrome = {}; var ChromeMAC = { - getDBPath: function() { + getDBPath: function() { return `${process.env.HOME}/Library/Application Support/Google/Chrome/${this.profile}/Cookies`; }, iterations: 1003, @@ -39,7 +39,7 @@ var ChromeMAC = { }; var ChromeLinux = { - getDBPath: function() { + getDBPath: function() { return `${process.env.HOME}/.config/google-chrome/${this.profile}/Cookies`; }, iterations: 1, @@ -52,7 +52,7 @@ var ChromeLinux = { }; var ChromeWindows = { - getDBPath: function() { + getDBPath: function() { return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`); }, getPassword: function(cb) { cb(); } diff --git a/plugins/cookie.firefox.js b/plugins/cookie.firefox.js index de2db29..f69f869 100644 --- a/plugins/cookie.firefox.js +++ b/plugins/cookie.firefox.js @@ -48,7 +48,7 @@ function getCookies(cb) { }); }); }); -}; +} plugin.signin = function(user, cb) { log.debug('running cookie.firefox.signin'); diff --git a/plugins/lintcode.js b/plugins/lintcode.js index 18b5c9d..073d12d 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -218,9 +218,9 @@ function runCode(problem, isTest, cb) { 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 + problem_id: problem.id, + code: file.data(problem.file), + language: lang.text }; if (isTest) { opts.form.input = problem.testcase; @@ -330,8 +330,8 @@ plugin.login = function(user, cb) { 'x-csrftoken': null }, form: { - username_or_email: user.login, - password: user.pass + username_or_email: user.login, + password: user.pass } }; diff --git a/plugins/solution.discuss.js b/plugins/solution.discuss.js index 1289a13..c155637 100644 --- a/plugins/solution.discuss.js +++ b/plugins/solution.discuss.js @@ -1,5 +1,4 @@ var _ = require('underscore'); -var cheerio = require('cheerio'); var request = require('request'); var log = require('../log'); @@ -24,7 +23,7 @@ function getSolution(problem, lang, cb) { var opts = { url: URL_DISCUSSES, json: true, - qs: { + qs: { query: [ 'query fetchTopics($questionId: Int!, $pageNo: Int!, $orderBy: String!) {', ' questionTopics(questionId: $questionId, pageNo: $pageNo, orderBy: $orderBy) {', @@ -42,8 +41,9 @@ function getSolution(problem, lang, cb) { ' }', '}' ].join('\n'), + operationName: 'fetchTopics', - variables: JSON.stringify({ + variables: JSON.stringify({ pageNo: 1, orderBy: 'most_votes', questionId: problem.id From 9de04127984e08d7d52dc0545c03291d2a69fc97 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sat, 24 Nov 2018 22:33:01 +0800 Subject: [PATCH 18/21] enable travis Signed-off-by: Eric Wang --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a6f5aa8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - 10 +os: + - linux +install: + - npm install +script: + - npm test From 6a33c98b09cc6a2332b42df3186cb1f80febb845 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 25 Nov 2018 22:19:05 +0800 Subject: [PATCH 19/21] refs #22: show chinese question name. Signed-off-by: Eric Wang --- plugins/leetcode.cn.js | 98 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/plugins/leetcode.cn.js b/plugins/leetcode.cn.js index c9b1aef..07d5d2d 100644 --- a/plugins/leetcode.cn.js +++ b/plugins/leetcode.cn.js @@ -1,12 +1,18 @@ +'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.18', +var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25', 'Plugin to talk with leetcode-cn APIs.'); plugin.init = function() { @@ -27,4 +33,94 @@ plugin.init = function() { config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id'; }; +// 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.fid]; + if (title) + problem.name = 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', + ' question {', + ' 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.question.questionId] = x.title; + }); + + return cb(null, titles); + }); +}; + module.exports = plugin; From e0b4613c842f69a04bcf84e836d929f8ba774e5d Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 3 Feb 2019 12:27:53 +0800 Subject: [PATCH 20/21] fixes #28: [solution] fixes 400 error Signed-off-by: Eric Wang --- plugins/solution.discuss.js | 46 +++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/plugins/solution.discuss.js b/plugins/solution.discuss.js index c155637..b3a9a13 100644 --- a/plugins/solution.discuss.js +++ b/plugins/solution.discuss.js @@ -11,7 +11,7 @@ var session = require('../session'); // // https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/solution.discuss.md // -var plugin = new Plugin(200, 'solution.discuss', '2018.04.14', +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'; @@ -20,14 +20,22 @@ 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, - qs: { + body: { query: [ - 'query fetchTopics($questionId: Int!, $pageNo: Int!, $orderBy: String!) {', - ' questionTopics(questionId: $questionId, pageNo: $pageNo, orderBy: $orderBy) {', - ' data {', + '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 {', @@ -42,11 +50,14 @@ function getSolution(problem, lang, cb) { '}' ].join('\n'), - operationName: 'fetchTopics', + operationName: 'questionTopicsList', variables: JSON.stringify({ - pageNo: 1, + query: '', + first: 1, + skip: 0, orderBy: 'most_votes', - questionId: problem.id + questionId: '' + problem.id, + tags: [lang] }) } }; @@ -55,23 +66,8 @@ function getSolution(problem, lang, cb) { if (resp.statusCode !== 200) return cb({msg: 'http error', statusCode: resp.statusCode}); - var langs = [lang]; - // try to find more compatible langs - if (lang === 'cpp') langs.push('c++'); - if (lang === 'csharp') langs.push('c#'); - if (lang === 'golang') langs.push('go'); - if (lang === 'javascript') langs.push('js'); - if (lang === 'python3') langs.push('python'); - - var solutions = body.data.questionTopics.data; - var solution = _.find(solutions, function(x) { - var keys = x.title.toLowerCase().split(/[^\w+]/); - for (var i = 0; i < keys.length; ++i) { - if (langs.indexOf(keys[i]) >= 0) return true; - } - return false; - }); - + const solutions = body.data.questionTopicsList.edges; + const solution = solutions.length > 0 ? solutions[0].node : null; return cb(null, solution); }); } From 752e94e8a755106106af65af9d9b831d9810c4cd Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Sun, 3 Feb 2019 12:39:39 +0800 Subject: [PATCH 21/21] fixes ut error Signed-off-by: Eric Wang --- plugins/solution.discuss.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/solution.discuss.js b/plugins/solution.discuss.js index b3a9a13..e78b90a 100644 --- a/plugins/solution.discuss.js +++ b/plugins/solution.discuss.js @@ -1,4 +1,3 @@ -var _ = require('underscore'); var request = require('request'); var log = require('../log'); @@ -25,7 +24,7 @@ function getSolution(problem, lang, cb) { var opts = { url: URL_DISCUSSES, json: true, - body: { + 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) {',