Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions lib/config.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,25 +31,36 @@ const DEFAULT_CONFIG = {
'swift'
],
urls: {
base: 'https://leetcode.com',
graphql: 'https://leetcode.com/graphql',
login: 'https://leetcode.com/accounts/login/',
// third part login base urls. TODO facebook google
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
problems: 'https://leetcode.com/api/problems/$category/',
problem: 'https://leetcode.com/problems/$slug/description/',
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
session: 'https://leetcode.com/session/',
submit: 'https://leetcode.com/problems/$slug/submit/',
submissions: 'https://leetcode.com/api/submissions/$slug',
submission: 'https://leetcode.com/submissions/detail/$id/',
verify: 'https://leetcode.com/submissions/detail/$id/check/',
favorites: 'https://leetcode.com/list/api/questions',
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
}
// base urls
base: 'https://leetcode.com',
graphql: 'https://leetcode.com/graphql',
login: 'https://leetcode.com/accounts/login/',
// third part login base urls. TODO facebook google
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
// redirect urls
leetcode_redirect: 'https://leetcode.com/',
github_tf_redirect: 'https://github.com/sessions/two-factor',
// simulate login urls
github_login_request: 'https://github.com/login',
github_session_request: 'https://github.com/session',
github_tf_session_request: 'https://github.com/sessions/two-factor',
linkedin_login_request: 'https://www.linkedin.com',
linkedin_session_request: 'https://www.linkedin.com/uas/login-submit',
// questions urls
problems: 'https://leetcode.com/api/problems/$category/',
problem: 'https://leetcode.com/problems/$slug/description/',
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
session: 'https://leetcode.com/session/',
submit: 'https://leetcode.com/problems/$slug/submit/',
submissions: 'https://leetcode.com/api/submissions/$slug',
submission: 'https://leetcode.com/submissions/detail/$id/',
verify: 'https://leetcode.com/submissions/detail/$id/check/',
favorites: 'https://leetcode.com/list/api/questions',
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
},
},

// but you will want change these
Expand Down
32 changes: 18 additions & 14 deletions lib/plugins/leetcode.cn.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,20 +17,24 @@ var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25',

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';
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';
// third parties
config.sys.urls.github_login = 'https://leetcode-cn.com/accounts/github/login/?next=%2F';
config.sys.urls.linkedin_login = 'https://leetcode-cn.com/accounts/linkedin_oauth2/login/?next=%2F';
config.sys.urls.leetcode_redirect = 'https://leetcode-cn.com/';
};

// FIXME: refactor those
Expand Down
91 changes: 60 additions & 31 deletions lib/plugins/leetcode.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -333,7 +333,7 @@ plugin.getSubmissions = function(problem, cb) {

// FIXME: this only return the 1st 20 submissions, we should get next if necessary.
const submissions = JSON.parse(body).submissions_dump;
for (let submission of submissions)
for (const submission of submissions)
submission.id = _.last(_.compact(submission.url.split('/')));

return cb(null, submissions);
Expand DownExpand Up@@ -471,8 +471,8 @@ plugin.deleteSession = function(session, cb) {
};

plugin.signin = function(user, cb) {
log.debug('running leetcode.signin');
const spin = h.spin('Signing in leetcode.com');
const isCN = config.app === 'leetcode.cn';
const spin = isCN ? h.spin('Signing in leetcode-cn.com') : h.spin('Signing in leetcode.com');
request(config.sys.urls.login, function(e, resp, body) {
spin.stop();
e = plugin.checkError(e, resp, 200);
Expand DownExpand Up@@ -538,11 +538,18 @@ plugin.login = function(user, cb) {
});
};

function parseCookie(cookie, cb) {
function parseCookie(cookie, body, cb) {
const isCN = config.app === 'leetcode.cn';
const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
const csrfPattern = /csrftoken=(.+?)(;|$)/;
let csrfPattern;
// leetcode-cn.com Cookie is not the same as leetcode.com in third parties
if (isCN) {
csrfPattern = /name="csrfmiddlewaretoken" value="(.*?)"/;
} else {
csrfPattern = /csrftoken=(.+?)(;|$)/;
}
const reSessionResult = SessionPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(isCN? body: cookie);
if (reSessionResult === null || reCsrfResult === null) {
return cb('invalid cookie?');
}
Expand All@@ -552,11 +559,18 @@ function parseCookie(cookie, cb) {
};
}

function saveAndGetUser(user, cb, cookieData) {
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
function requestLeetcodeAndSave(request, leetcodeUrl, user, cb) {
request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== config.sys.urls.leetcode_redirect) {
return cb('Login failed. Please make sure the credential is correct.');
}
const cookieData = parseCookie(resp.request.headers.cookie, body, cb);
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
});
}

plugin.cookieLogin = function(user, cb) {
Expand All@@ -568,15 +582,16 @@ plugin.cookieLogin = function(user, cb) {
};

plugin.githubLogin = function(user, cb) {
const leetcodeUrl = config.sys.urls.github_login;
const urls = config.sys.urls;
const leetcodeUrl = urls.github_login;
const _request = request.defaults({jar: true});
_request('https://github.com/login', function(e, resp, body) {
_request(urls.github_login_request, function(e, resp, body) {
const authenticityToken = body.match(/name="authenticity_token" value="(.*?)"/);
if (authenticityToken === null) {
return cb('Get GitHub token failed');
}
const options = {
url: 'https://github.com/session',
url: urls.github_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Expand All@@ -594,27 +609,48 @@ plugin.githubLogin = function(user, cb) {
if (resp.statusCode !== 200) {
return cb('GitHub login failed');
}
_request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== 'https://leetcode.com/') {
return cb('GitHub login failed or GitHub did not link to LeetCode');
if (resp.request.uri.href !== urls.github_tf_redirect) {
return requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
}
// read two-factor code must be sync.
const twoFactorcode = require('prompt-sync')()('Please enter your two-factor code: ');
const authenticityTokenTwoFactor = body.match(/name="authenticity_token" value="(.*?)"/);
if (authenticityTokenTwoFactor === null) {
return cb('Get GitHub two-factor token failed');
}
const optionsTwoFactor = {
url: urls.github_tf_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
followAllRedirects: true,
form: {
'otp': twoFactorcode,
'authenticity_token': authenticityTokenTwoFactor[1],
'utf8': encodeURIComponent('✓'),
},
};
_request(optionsTwoFactor, function(e, resp, body) {
if (resp.request.uri.href === urls.github_tf_session_request) {
return cb('Invalid two-factor code please check');
}
const cookieData = parseCookie(resp.request.headers.cookie, cb);
saveAndGetUser(user, cb, cookieData);
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
});
});
});
};

plugin.linkedinLogin = function(user, cb) {
const leetcodeUrl = config.sys.urls.linkedin_login;
const urls = config.sys.urls;
const leetcodeUrl = urls.linkedin_login;
const _request = request.defaults({
jar: true,
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
});
_request('https://www.linkedin.com', function(e, resp, body) {
_request(urls.linkedin_login_request, function(e, resp, body) {
if ( resp.statusCode !== 200) {
return cb('Get LinkedIn session failed');
}
Expand All@@ -623,7 +659,7 @@ plugin.linkedinLogin = function(user, cb) {
return cb('Get LinkedIn token failed');
}
const options = {
url: 'https://www.linkedin.com/uas/login-submit',
url: urls.linkedin_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Expand All@@ -640,14 +676,7 @@ plugin.linkedinLogin = function(user, cb) {
if (resp.statusCode !== 200) {
return cb('LinkedIn login failed');
}
_request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== 'https://leetcode.com/') {
return cb('LinkedIn login failed or LinkedIn did not link to LeetCode');
}
const cookieData = parseCookie(resp.request.headers.cookie, cb);
saveAndGetUser(user, cb, cookieData);
});
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
});
});
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,6 +71,7 @@
"nock": "10.0.2",
"nyc": "^13.3.0",
"pkg": "^4.3.4",
"prompt-sync": "^4.2.0",
"rewire": "4.0.1"
}
}