leetcode-fetcher-cli
Version:
A CLi Application for local fetching of leetcode problems
231 lines (230 loc) • 9.82 kB
JavaScript
;
/**
* @author Riccardo La Marca
*
* @brief User-releated commands:
* - login [Login into leetcode to start a session]
* - inspect [Inspect the overall statistics of a user, or the current logged one]
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspect_command = exports.login_command = void 0;
const crypto_1 = require("crypto");
const pprint_1 = require("../pprint");
const general_1 = require("../utils/general");
const formatter_1 = require("../utils/formatter");
const printer_1 = require("../utils/printer");
const lc = __importStar(require("../leetcode"));
const constants_1 = __importDefault(require("../constants"));
const chalk_1 = __importDefault(require("chalk"));
const SetLoginDetails = (state, params, user_str, pwd_str) => {
let content = {};
for (const [key, value] of params.entries()) {
if ([user_str, pwd_str].includes(key)) {
content[key] = value;
}
}
// Creates the salt and hash the password
const salt = (0, crypto_1.randomBytes)(16).toString('hex');
const hash_pwd = (0, general_1.HashPassword)(content[pwd_str], salt);
state.userLogin = { username: content[user_str], password: hash_pwd, salt: salt };
state.selectedUser = content[user_str];
};
const HandleResponse = async (state, response) => {
// Classical login through LeetCode
if (response.url().startsWith(constants_1.default.SITES.LOGIN_PAGE.URL)) {
const request = response.request(); // Get the corresponding request
// We dont want the navigation request since most likely it would
// be the naviagation request we have done when opening the page.
// Moreover, we also need the response to be successful and the
// request to be a POST request, thus with POST data.
if (request.isNavigationRequest() || !request.hasPostData() || !response.ok())
return;
// Get the username and the password provided and compute the hash
const result = await response.json();
const username = result.form.fields.login.value;
const password = result.form.fields.password.value;
const salt = (0, crypto_1.randomBytes)(16).toString('hex');
const hash_pawd = (0, general_1.HashPassword)(password, salt);
state.userLogin = { username: username, password: hash_pawd, salt: salt };
state.selectedUser = username;
const headers = response.headers();
const matches = headers['set-cookie'].matchAll(/^([\w_]+)=([^;]+)/gm);
state.cookies = { csrftoken: "", messages: "", LEETCODE_SESSION: "" };
Array.from(matches).forEach((value) => {
state.cookies[value[1]] = value[2];
});
return;
}
// Login using Github
if (response.url().startsWith(constants_1.default.SITES.THIRD_PARTY.GITHUB.SESSION)) {
const response_status_code = response.status();
const request = response.request();
// If the response is not a redirect or the request has no post data
if (response_status_code !== 302 || !request.hasPostData())
return;
const request_post_data = request.postData();
const params = new URLSearchParams(request_post_data);
// From the (key, value) pairs select those corresponding to username and password
SetLoginDetails(state, params, "login", "password");
return;
}
if ((response.url().startsWith(constants_1.default.SITES.THIRD_PARTY.GITHUB.CALLBACK)
|| response.url().startsWith(constants_1.default.SITES.THIRD_PARTY.LINKEDIN.CALLBACK))) {
const headers = response.headers();
const matches = headers['set-cookie'].matchAll(/^([\w_]+)=([^;]+)/gm);
state.cookies = { csrftoken: "", messages: "", LEETCODE_SESSION: "" };
Array.from(matches).forEach((value) => {
state.cookies[value[1]] = value[2];
});
return;
}
// Login using Linkedin
if (response.url().startsWith(constants_1.default.SITES.THIRD_PARTY.LINKEDIN.SESSION)) {
const response_status_code = response.status();
const request = response.request();
// If the response is not a redirect or the request has no post data
if (response_status_code !== 303 || !request.hasPostData())
return;
const request_post_data = request.postData();
const params = new URLSearchParams(request_post_data);
SetLoginDetails(state, params, "session_key", "session_password");
return;
}
};
const HandleRequest = async (request) => {
if (request.isInterceptResolutionHandled())
return;
if (request.url() !== constants_1.default.SITES.HOME_PAGE.URL) {
request.continue();
return;
}
request.respond(constants_1.default.SITES.HOME_PAGE.REDIRECT);
};
const CheckUserSession = async (spinner, state) => {
if (state.userLogin !== undefined && state.cookies !== undefined) {
spinner.changeMessage("Session found. Checking validity");
spinner.start();
const result = await lc.CheckUserSession(state.cookies);
spinner.stop();
if (result) {
console.log((0, formatter_1.FormatString)("\rThe current {0} is still {1}!", chalk_1.default.bold("session"), chalk_1.default.greenBright("active")));
}
else {
state.userLogin = undefined;
state.cookies = undefined;
}
}
};
const LoginCommand = async (data, state) => {
const spinner = new pprint_1.Spinner("User Logging in");
const forced = data[0] !== undefined;
if (!forced)
await CheckUserSession(spinner, state);
else {
console.warn(chalk_1.default.yellowBright("[WARNING] Login has been forced. No check on current session."));
}
if (!(state.userLogin && state.cookies) || forced) {
spinner.changeMessage("User Logging in");
spinner.start();
try {
await (0, general_1.OpenLoginBrowser)((r) => HandleResponse(state, r), HandleRequest);
}
catch (error) {
}
spinner.stop();
}
if (!(state.userLogin && state.cookies)) {
console.log(chalk_1.default.red('User login has been stopped.'));
state.userLogin = undefined;
state.cookies = undefined;
return state;
}
let headers = (0, formatter_1.FormatCookies)(state.cookies);
const username = await lc.FetchUsername(headers);
if (!username) {
console.error(chalk_1.default.redBright(`User ${state.userLogin.username} does not have access.`));
state.userLogin = undefined;
state.cookies = undefined;
return state;
}
state.userLogin.username = username;
const success = chalk_1.default.greenBright("successfully");
console.log(`User ${username} has ${success} logged.`);
// Fetch user details
const user_data = await lc.GetUserData(state.userLogin.username, state);
state.profile = user_data;
if (user_data)
(0, printer_1.PrintUserSummary)(user_data);
return state;
};
// Login command - Login into leetcode to start a session
exports.login_command = {
group: 'User',
name: 'Login Command',
command: 'login',
syntax: /^login(?:\s+(force))?$/,
callback: LoginCommand,
help: 'login [force] - Login into leetcode to start a session. `force` parameter\n' +
'forces the application to attempt the login instead of checking the\n' +
'current session validity (if any).'
};
const InspectCommand = async (data, state) => {
if (data.length < 1) {
// Check if a user is logged
if (!state.selectedUser) {
console.error(chalk_1.default.redBright("No current logged user. A username must be provided."));
return state;
}
data = [state.selectedUser];
}
const result = await lc.GetUserData(data[0], state);
if (result)
(0, printer_1.PrintUserSummary)(result);
return state;
};
// Inspect command - Inspect a user
exports.inspect_command = {
group: 'User',
name: 'Inspect Command',
command: 'inspect',
syntax: /^inspect(?:\s(.*?))$/,
callback: InspectCommand,
help: 'inspect [USERNAME] - Inspect a given username if it exists' +
', or the current logged one.\n'
};