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);
});
});
};