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/.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 diff --git a/README.md b/README.md index e5f7803..ecda979 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,45 @@ # leetcode-cli-plugins + + + 3rd party plugins for leetcode-cli. -|Plugin|Description|Enhanced Commands| -|-|-|-| -|[company](/docs/company.md)|Filter questions by company|`list`| +* what is [leetcode-cli](https://github.com/skygragon/leetcode-cli) +* how to use [leetcode-cli plugins](https://skygragon.github.io/leetcode-cli/commands#plugin) +* how to use [leetcode-cli configs](https://skygragon.github.io/leetcode-cli/advanced#configuration) + +## Plugins + +|Name|Description|Enhanced Commands| +|----|-----------|-----------------| +|[company](/docs/company.md)|Filter questions by company or tags|`list`| +|[cookie.chrome](/docs/cookie.chrome.md)|Don't expire Chrome's session on same computer|`login`| +|[cookie.firefox](/docs/cookie.firefox.md)|Don't expire Firefox's session on same computer|`login`| |[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`| -## HOWTO - -You can install the plugins in either ways below: - -### Quick Install +## Quick Start - $ leetcode plugin -i + $ leetcode plugin -i # install + $ leetcode plugin -d # disable + $ leetcode plugin -e # enable + $ leetcode plugin -D # delete -To manage the installed plugins, please check [leetcode-cli's user guide](https://skygragon.github.io/leetcode-cli/commands#plugin). + $ leetcode plugin -c # show config + $ leetcode config plugins: # show config + $ leetcode config plugins:: # set config + $ leetcode config -d plugins: # delete config -### Configuration +**Example** -Some plugins could be configured with your customized options. + $ leetcode plugin -i company # install compnay + $ leetcode plugin -D compnay # delete company -Check [leetcode-cli's configuration](https://skygragon.github.io/leetcode-cli/advanced#configuration) for more details. + $ leetcode plugin -c github # show github plugin config + $ leetcode config plugins:github # show github plugin config + $ leetcode config plugins:github:token 12345678 # set github plugin config + $ leetcode config -d plugins:github # delete github plugin config diff --git a/docs/cookie.chrome.md b/docs/cookie.chrome.md new file mode 100644 index 0000000..c5a9dbc --- /dev/null +++ b/docs/cookie.chrome.md @@ -0,0 +1,64 @@ +# cookie.chrome + +Leetcode.com only permits one live session for same account, which means if you login successfully via leetcode-cli, your existing session in browser will get expired, and vice versa. + +This plugin enables leetcode-cli to try to reuse the existing session in your chrome borwser on the same computer. + +If succeed, you can keep leetcode-cli and chrome both alive in the same time. + +If failed, it will fall back to normal login to leetcode.com as before. + + +## Requirement + +In short it's case by case since it varies on different platforms (Mac/Linux/Windows), different chrome versions, even different cookie encryption versions (v10/v11). + +### Linux + +Make sure `secret-tool` is available: + + $ sudo apt-get install libsecret-tools + +### Windows + +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. + + $ leetcode user -l -v + login: + pass: + [DEBUG] running cookie.chrome.login + [DEBUG] running cookie.chrome.signin + [DEBUG] try to copy leetcode cookies from chrome ... + [DEBUG] Successfully copied leetcode cookies! + [DEBUG] running leetcode.getFavorites + Successfully login as + diff --git a/docs/cookie.firefox.md b/docs/cookie.firefox.md new file mode 100644 index 0000000..f3f413a --- /dev/null +++ b/docs/cookie.firefox.md @@ -0,0 +1,18 @@ +# cookie.firefox + +Same as [cookie.chrome](https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md), but works for Firefox. + +## Usage + +If enabled, the login will try to reuse existing Firefox cookies. You can verify it by printing debug output as below. + + $ leetcode user -l -v + login: + pass: + [DEBUG] running cookie.firefox.login + [DEBUG] running cookie.firefox.signin + [DEBUG] try to copy leetcode cookies from firefox ... + [DEBUG] Successfully copied leetcode cookies! + [DEBUG] running leetcode.getFavorites + Successfully login as + diff --git a/docs/cpp.lint.md b/docs/cpp.lint.md index f5bfb3f..5ad4d57 100644 --- a/docs/cpp.lint.md +++ b/docs/cpp.lint.md @@ -4,7 +4,7 @@ Run cpplint to check c++ code syntax before running `test` against leetcode.com. ## Requirement -* `cpplint.py`: [download](https://raw.githubusercontent.com/google/styleguide/gh-pages/cpplint/cpplint.py) +* need install`cpplint.py`: [download](https://raw.githubusercontent.com/google/styleguide/gh-pages/cpplint/cpplint.py) ## Config @@ -13,12 +13,21 @@ Run cpplint to check c++ code syntax before running `test` against leetcode.com. * `+` to enable specific checking. * `-` to disable specific checking. +*Set Config* + + $ leetcode config plugins:cpp.lint:bin + $ leetcode config plugins:cpp.lint:flags '["-whitespace/indent"]' + +*Unset Config* + + $ leetcode config -d plugins:cpp.lint + *Example* { - "PLUGINS": { + "plugins": { "cpp.lint": { - "bin": "", + "bin": "/usr/bin/cpplint.py", "flags": [ "-whitespace/indent" ] diff --git a/docs/cpp.run.md b/docs/cpp.run.md index 53e55ae..2f0c0c2 100644 --- a/docs/cpp.run.md +++ b/docs/cpp.run.md @@ -10,7 +10,7 @@ NOTE: not fully support all the questions! ## Requirement -* `g++` with c++11 support. +* need install `g++` with c++11 support. ## Usage diff --git a/docs/github.md b/docs/github.md index d6fcd33..d756942 100644 --- a/docs/github.md +++ b/docs/github.md @@ -2,15 +2,28 @@ Auto commit the accepted code to your own (public) GitHub repo. +## Requirement + +* create an access token for your github repo. + ## Config * `repo`: your GitHub repo to persistent accpeted code. * `token`: the access token to your repo, see [how to get token](#generate-token). +*Set Config* + + $ leetcode config plugins:github:repo + $ leetcode config plugins:github:token + +*Unset Config* + + $ leetcode config -d plugins:github + *Example* { - "PLUGINS": { + "plugins": { "github": { "repo": "https://github.com/skygragon/test", "token": "xxxxxxxxxxxxx" 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. diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000..cb69d3f Binary files /dev/null and b/docs/logo.png differ 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 a383bf0..ac48974 100644 --- a/plugins/company.js +++ b/plugins/company.js @@ -1,10 +1,11 @@ var Plugin = require('../plugin'); -// [prerequisite] // -// [config] +// [Usage] // -var plugin = new Plugin(100, 'company', '2017.07.25', +// 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: @@ -12,77 +13,79 @@ var plugin = new Plugin(100, 'company', '2017.07.25', // * lintcode.com // * 1point3acres.com // * other results from google search -var DATA = { - '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', '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'], +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'], @@ -141,7 +144,7 @@ var DATA = { '174': ['microsoft'], '186': ['amazon', 'microsoft', 'uber'], '187': ['linkedin'], - '189': ['bloomberg', 'microsoft'], + '189': ['amazon', 'bloomberg', 'microsoft'], '190': ['airbnb', 'apple'], '191': ['apple', 'microsoft'], '195': ['adobe'], @@ -152,7 +155,7 @@ var DATA = { '204': ['amazon', 'microsoft'], '205': ['linkedin'], '206': ['adobe', 'amazon', 'apple', 'bloomberg', 'facebook', 'microsoft', 'snapchat', 'twitter', 'uber', 'yahoo', 'yelp', 'zenefits'], - '207': ['apple', 'yelp', 'zenefits'], + '207': ['apple', 'uber', 'yelp', 'zenefits'], '208': ['bloomberg', 'facebook', 'google', 'microsoft', 'twitter', 'uber'], '209': ['facebook'], '210': ['facebook', 'zenefits'], @@ -308,7 +311,7 @@ var DATA = { '389': ['google'], '391': ['google'], '393': ['google'], - '394': ['google'], + '394': ['google', 'yelp'], '395': ['baidu'], '396': ['amazon'], '397': ['baidu', 'google'], @@ -341,6 +344,7 @@ var DATA = { '438': ['amazon'], '439': ['snapchat'], '442': ['pocketgems'], + '443': ['bloomberg', 'microsoft', 'snapchat', 'yelp'], '444': ['google'], '445': ['bloomberg', 'microsoft'], '446': ['baidu'], @@ -412,10 +416,12 @@ var DATA = { '543': ['facebook', 'google'], '544': ['google'], '545': ['amazon', 'google'], - '547': ['twosigma'], + '547': ['bloomberg', 'twosigma'], '548': ['alibaba'], + '549': ['google'], '551': ['google'], '552': ['google'], + '553': ['amazon'], '554': ['facebook'], '555': ['alibaba'], '556': ['bloomberg'], @@ -428,9 +434,737 @@ var DATA = { '567': ['microsoft'], '568': ['google'], '569': ['google'], - '572': ['ebay', 'google'], + '570': ['bloomberg'], + '572': ['ebay', 'facebook', 'google'], '576': ['baidu'], - '579': ['amazon'] + '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) { @@ -439,11 +1173,13 @@ plugin.getProblems = function(cb) { problems.forEach(function(problem) { var id = String(problem.id); - if (!(id in DATA)) return; - - problem.companies = DATA[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); }); }; diff --git a/plugins/cookie.chrome.js b/plugins/cookie.chrome.js new file mode 100644 index 0000000..e2d4278 --- /dev/null +++ b/plugins/cookie.chrome.js @@ -0,0 +1,184 @@ +var path = require('path'); + +var log = require('../log'); +var Plugin = require('../plugin'); +var Queue = require('../queue'); +var session = require('../session'); + +// [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']; + + db.serialize(function() { + var cookies = {}; + var sql = 'select name, encrypted_value from cookies where host_key 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.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/plugins/cookie.firefox.js b/plugins/cookie.firefox.js new file mode 100644 index 0000000..f69f869 --- /dev/null +++ b/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/plugins/cpp.lint.js b/plugins/cpp.lint.js index b8cdeca..a892cd5 100644 --- a/plugins/cpp.lint.js +++ b/plugins/cpp.lint.js @@ -3,24 +3,11 @@ var cp = require('child_process'); var log = require('../log'); var Plugin = require('../plugin'); -// [prerequisite] // -// Need install cpplint: -// https://raw.githubusercontent.com/google/styleguide/gh-pages/cpplint/cpplint.py +// [Usage] // -// [config] +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cpp.lint.md // -// You can disable/enable some checks in "flags" section. -// -// "PLUGINS": { -// "cpp.lint": { -// "bin": "", -// "flags": [ -// "-whitespace/indent", -// -// ] -// } -// } var plugin = new Plugin(100, 'cpp.lint', '2017.07.27', 'Plugin to do static code check on c++ code.'); @@ -30,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/cpp.run.js b/plugins/cpp.run.js index d9113e0..6f206f8 100644 --- a/plugins/cpp.run.js +++ b/plugins/cpp.run.js @@ -11,9 +11,10 @@ var session = require('../session'); // // Only works for those problems could be easily tested. // -// [prerequisite] +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cpp.run.md // -// - g++ (support c++11) var plugin = new Plugin(100, 'cpp.run', '2017.07.29', 'Plugin to run cpp code locally for debugging.'); diff --git a/plugins/github.js b/plugins/github.js index 4344ff1..6c94e9e 100644 --- a/plugins/github.js +++ b/plugins/github.js @@ -2,29 +2,29 @@ var path = require('path'); var url = require('url'); var h = require('../helper'); +var file = require('../file'); var log = require('../log'); var Plugin = require('../plugin'); -// [prerequisite] // -// - create a new access token on your github repo setting. (TBD) +// [Usage] // -// [config] +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/github.md // -// "PLUGINS": { -// "github": { -// "repo": "https://github.com///", -// "token": "" -// } -// } -var plugin = new Plugin(100, 'github', '2017.08.10', +var plugin = new Plugin(100, 'github', '2018.11.18', 'Plugin to commit accepted code to your own github repo.', - ['github']); + ['github@13']); var ctx = {}; plugin.submitProblem = function(problem, cb) { - var parts = url.parse(this.config.repo).pathname.split('/'); + // 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); @@ -35,7 +35,7 @@ plugin.submitProblem = function(problem, cb) { var GitHubApi = require('github'); var github = new GitHubApi({host: 'api.github.com'}); - github.authenticate({type: 'token', token: this.config.token}); + github.authenticate({type: 'token', token: plugin.config.token}); plugin.next.submitProblem(problem, function(_e, results) { cb(_e, results); @@ -48,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/leetcode.cn.js b/plugins/leetcode.cn.js new file mode 100644 index 0000000..07d5d2d --- /dev/null +++ b/plugins/leetcode.cn.js @@ -0,0 +1,126 @@ +'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'; +}; + +// 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; diff --git a/plugins/lintcode.js b/plugins/lintcode.js index bf0c950..073d12d 100644 --- a/plugins/lintcode.js +++ b/plugins/lintcode.js @@ -4,9 +4,11 @@ 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 Queue = require('../queue'); var session = require('../session'); // Still working in progress! @@ -14,36 +16,41 @@ var session = require('../session'); // TODO: star/submissions/submission // FIXME: why [ERROR] Error: read ECONNRESET [0]?? // -var plugin = new Plugin(15, 'lintcode', '2017.08.04', +// [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.'); -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/' -}; - -var LANGS = [ +// 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) { - var opts = {}; - opts.url = url; - opts.headers = {}; - + const opts = { + url: url, + headers: {} + }; if (session.isLogin()) signOpts(opts, session.getUser()); return opts; @@ -51,7 +58,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) { @@ -76,130 +83,128 @@ function _strip(s) { 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 = []; - 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.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); - 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: LEVELS[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'), ','); - }); - + 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; - }).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.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); - 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.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); @@ -209,36 +214,36 @@ 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.sys.urls.test); + opts.headers.referer = problem.link; opts.form = { - problem_id: problem.id, - code: h.getFileData(problem.file), - language: lang.text, - csrfmiddlewaretoken: session.getUser().sessionCSRF + 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 || !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); }); } 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) { @@ -254,6 +259,7 @@ function verifyResult(id, isTest, cb) { } function formatResult(result) { + spin.stop(); var x = { ok: result.status === 'Accepted', type: 'Actual', @@ -286,7 +292,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, @@ -318,33 +324,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.sys.urls.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); }); }; diff --git a/plugins/solution.discuss.js b/plugins/solution.discuss.js index abab796..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'); @@ -6,54 +5,94 @@ var chalk = require('../chalk'); var Plugin = require('../plugin'); var session = require('../session'); -var plugin = new Plugin(200, 'solution.discuss', '2017.07.29', +// +// [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://discuss.leetcode.com/api/category/'; -var URL_DISCUSS_TOPIC = 'https://discuss.leetcode.com/topic/'; +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 opts = { - url: URL_DISCUSSES + problem.discuss - }; - request(opts, function(e, resp, body) { + var lang = session.argv.lang; + getSolution(problem, lang, function(e, solution) { if (e) return cb(e); - if (resp.statusCode !== 200) - return cb({msg: 'http error', statusCode: resp.statusCode}); - - var lang = session.argv.lang; - 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 data = JSON.parse(body); - var solution = _.find(data.topics, function(x) { - var keys = x.title.toLowerCase().split(' '); - for (var i = 0; i < keys.length; ++i) { - if (langs.indexOf(keys[i]) >= 0) return true; - } - return false; - }); - - if (!solution) { - return log.error('Solution not found for ' + lang); - } - - log.info(solution._imported_title); + 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('* Votes: ' + solution.votes); + log.info('* Lang: ' + lang); + log.info('* Author: ' + solution.post.author.username); + log.info('* Votes: ' + solution.post.voteCount); log.info(); - log.info(chalk.yellow(solution._imported_content)); + log.info(content); }); }); };