leetcode-fetcher-cli
Version:
A CLi Application for local fetching of leetcode problems
299 lines (298 loc) • 14.7 kB
JavaScript
;
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.SubmitSolution = exports.TestSolution = exports.CheckUserSession = exports.FetchLeetcode = exports.GetUserData = exports.FetchShortSubmissionDetail = exports.FetchSubmissionDetail = exports.FetchUserRecentAcSubmissions = exports.FetchUserRecentSubmissions = exports.FetchUserLanguageStats = exports.FetchUserProfile = exports.FetchUsername = exports.FetchDailyQuestion = exports.FetchQuestion = exports.FetchProblemList = exports.FetchNumberOfProblems = exports.FetchGraphQLData = void 0;
const pprint_1 = require("./pprint");
const queries_1 = __importDefault(require("./queries"));
const constants_1 = __importDefault(require("./constants"));
const chalk_1 = __importDefault(require("chalk"));
const formatter = __importStar(require("./utils/formatter"));
const generic = __importStar(require("./utils/general"));
const FetchGraphQLData = async (variables, formatData, query, headers, url) => {
try {
// Sends a post request to the graphql endpoint of leetcode
const response = await fetch(url, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ query, variables }),
});
// Obtain the json response representation
const result = await response.json();
return formatData(result.data);
}
catch (error) {
console.error("Error when fetching question:", error);
return null;
}
};
exports.FetchGraphQLData = FetchGraphQLData;
const createGraphQLFetcher = (formatData, query, url, headers) => {
return (variables) => {
return (0, exports.FetchGraphQLData)(variables, formatData, query, { ...(headers ?? { Referer: 'https://leetcode.com' }), }, url);
};
};
/**
* Fetches the total number of problems for the input difficulty
*/
const FetchNumberOfProblems = async (difficulty) => {
return createGraphQLFetcher((data) => data.problemsetQuestionList.total, queries_1.default.problemset.problemsetQuestionList, constants_1.default.SITES.GRAPHQL.URL)({
categorySlug: "",
limit: 1,
skip: 0,
filters: { difficulty }
});
};
exports.FetchNumberOfProblems = FetchNumberOfProblems;
/**
* Fetches a list of problems from LeetCode interacting with the GraphQL endpoint.
* The problems are also filtered using the `variables` input.
*/
const FetchProblemList = (variables, header) => {
return createGraphQLFetcher((x) => x, queries_1.default.problemset.problemsetQuestionList, constants_1.default.SITES.GRAPHQL.URL, header)(variables);
};
exports.FetchProblemList = FetchProblemList;
/**
* Fetches the details for a single question using the LeetCode GraphQL Endpoint.
*/
const FetchQuestion = async (variables, header) => {
return createGraphQLFetcher((x) => x, queries_1.default.problemset.selectProblem, constants_1.default.SITES.GRAPHQL.URL, header)(variables);
};
exports.FetchQuestion = FetchQuestion;
/**
* Fetches the current daily question from LeetCode through the graphql endpoint.
*/
const FetchDailyQuestion = async () => {
return createGraphQLFetcher((x) => x, queries_1.default.problemset.questionOfToday, constants_1.default.SITES.GRAPHQL.URL)({});
};
exports.FetchDailyQuestion = FetchDailyQuestion;
/**
* Fetches the username of the current logged user, or the current user session
*/
const FetchUsername = async (header) => {
return createGraphQLFetcher((x) => x.userStatus.username, queries_1.default.user.userStatus, constants_1.default.SITES.GRAPHQL.URL, header)({});
};
exports.FetchUsername = FetchUsername;
/**
* Fetches the input user profile from LeetCode through the graphql endpoint.
*/
const FetchUserProfile = async (vars, headers) => {
return createGraphQLFetcher((x) => x, queries_1.default.user.userPublicProfile, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchUserProfile = FetchUserProfile;
/**
* Fetches the input user language stats from LeetCode through the graphql endpoint.
* For each programming languge the corresponding count of accepted or attempted
* submissions is returned.
*/
const FetchUserLanguageStats = async (vars, headers) => {
return createGraphQLFetcher((x) => x, queries_1.default.user.languageStats, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchUserLanguageStats = FetchUserLanguageStats;
/**
* Fetches the input user list of all recent submissions from LeetCode through the graphql endpoint.
*/
const FetchUserRecentSubmissions = async (vars, headers) => {
return createGraphQLFetcher((x) => x, queries_1.default.user.recentSubmissions, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchUserRecentSubmissions = FetchUserRecentSubmissions;
/**
* Fetches the input user list of all recent accepted submissions from LeetCode through the graphql endpoint.
*/
const FetchUserRecentAcSubmissions = async (vars, headers) => {
return createGraphQLFetcher((x) => x, queries_1.default.user.recentAcSubmissions, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchUserRecentAcSubmissions = FetchUserRecentAcSubmissions;
/**
* Fetches the details for the input submission ID
*/
const FetchSubmissionDetail = async (vars, headers) => {
return createGraphQLFetcher((x) => x, queries_1.default.solveql.submissionDetails, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchSubmissionDetail = FetchSubmissionDetail;
const FetchShortSubmissionDetail = async (vars, headers) => {
return createGraphQLFetcher(formatter.FormatShortSubmissionDetails, queries_1.default.solveql.submissionDetails, constants_1.default.SITES.GRAPHQL.URL, headers)(vars);
};
exports.FetchShortSubmissionDetail = FetchShortSubmissionDetail;
const ToShortSubmission = async (submissions, state) => {
let headers = formatter.FormatCookies(state.cookies);
if (!headers) {
console.warn(chalk_1.default.yellowBright("To fetch submission details a session must exists."));
}
const submission_list = await Promise.all(submissions.map(async (x) => {
const short_data = await (0, exports.FetchShortSubmissionDetail)({ submissionId: x.id }, headers);
return { ...x, ...short_data };
}));
return { submissionList: submission_list };
};
const GetUserData = async (username, state) => {
const spinner = new pprint_1.Spinner(`Fetching User ${username} profile`);
spinner.start();
const variables = { username: username };
let headers = formatter.FormatCookies(state.cookies);
const user_profile = await (0, exports.FetchUserProfile)(variables, headers);
// Check that the provided user exists
if (user_profile?.matchedUser === null) {
spinner.stop();
console.error(chalk_1.default.redBright(formatter.FormatString("User {0} does not exists", chalk_1.default.bold(username))));
return undefined;
}
const lang_stats = await (0, exports.FetchUserLanguageStats)(variables);
const recent_submissions = await (0, exports.FetchUserRecentSubmissions)(variables);
const recent_ac_submissions = await (0, exports.FetchUserRecentAcSubmissions)({ ...variables, limit: -1 });
spinner.changeMessage(`Fetching User ${username} submissions`);
const short_ac_submission = await ToShortSubmission(recent_ac_submissions?.recentAcSubmissionList, state);
const short_submission = await ToShortSubmission(recent_submissions?.recentSubmissionList, state);
spinner.stop();
// Construct the user type with the fetched informations
return formatter.FormatUserData(user_profile, lang_stats, short_submission, short_ac_submission);
};
exports.GetUserData = GetUserData;
const FetchLeetcode = async (url, method, headers, body) => {
try {
// Sends a post request to the graphql endpoint of leetcode
const response = await fetch(url, { method: method, headers: headers, credentials: "include", body: (body ?? null) });
return response;
}
catch (error) {
console.error(chalk_1.default.redBright(`Error when fetching leetcode URL: ${error}`));
return null;
}
};
exports.FetchLeetcode = FetchLeetcode;
const CheckUserSession = async (cookies) => {
for (let i = 0; i < constants_1.default.SITES.CONNECTION_ATTEMPT; i++) {
const cookie_s = `LEETCODE_SESSION=${cookies.LEETCODE_SESSION}; ` +
`csrftoken=${cookies.csrftoken};`;
const response = await (0, exports.FetchLeetcode)(constants_1.default.SITES.PROBLEMSET_PAGE.URL, "GET", { ...constants_1.default.SITES.GENERIC_HEADERS, "Cookie": cookie_s });
if (response && response.ok && response.status === 200)
return true;
await new Promise(resolve => { setTimeout(resolve, 2000); });
}
return false;
};
exports.CheckUserSession = CheckUserSession;
const FormatSubmissionRequest = (problem, cookies, folder, test) => {
const problem_id = problem.questionId;
const problem_frontend_id = Number.parseInt(problem.questionFrontendId);
const problem_title = problem.titleSlug;
const f_cookies = formatter.FormatCookies(cookies);
const request_headers = {
...constants_1.default.SITES.GENERIC_HEADERS,
...f_cookies,
Referer: `https://leetcode.com/problems/${problem_title}/`,
Origin: 'https://leetcode.com',
"Content-Type": 'application/json',
"x-csrftoken": cookies.csrftoken
};
const solution = generic.ReadProblemSolution(folder, problem_frontend_id, problem_title);
if (!solution)
return [undefined, undefined, undefined];
const request_body = { lang: "python3", question_id: problem_id.toString(), typed_code: solution };
if (test) {
const test_cases = generic.ReadProblemTestCases(folder, problem_frontend_id, problem_title);
if (!test_cases)
return [undefined, undefined, undefined];
request_body.data_input = test_cases.join('\n');
}
const request_body_str = JSON.stringify(request_body);
const url_prefix = `https://leetcode.com/problems/${problem_title}`;
const request_url = (test) ? `${url_prefix}/interpret_solution/` : `${url_prefix}/submit/`;
return [request_headers, request_body_str, request_url];
};
const WaitForResults = async (id, headers) => {
const result_url = `https://leetcode.com/submissions/detail/${id}/check/`;
let json_result;
do {
const results = await (0, exports.FetchLeetcode)(result_url, 'GET', headers);
if (!(results && results.ok && results.status === 200)) {
console.error(chalk_1.default.redBright(`Check request terminated with status code: ${results.status}`));
return null;
}
json_result = await results.json();
await new Promise(resolve => { setTimeout(resolve, 100); });
} while (json_result.state !== 'SUCCESS');
return json_result;
};
const RunSolution = async (state, msg, test) => {
const spinner = new pprint_1.Spinner(msg);
spinner.start();
const root_folder = state.variables["FOLDER"].value;
const [request_headers, request_body, url] = FormatSubmissionRequest(state.watchQuestion, state.cookies, root_folder, test);
if (!request_headers || !request_body || !url) {
spinner.stop();
return null;
}
const response = await (0, exports.FetchLeetcode)(url, 'POST', request_headers, request_body);
if (!(response && response.ok && response.status === 200)) {
spinner.stop();
console.error(chalk_1.default.redBright(`Test request terminated with status code: ${response.status}`));
return null;
}
spinner.changeMessage("Waiting for results");
const response_data = await response.json();
const request_id = (test) ? response_data.interpret_id : response_data.submission_id;
const json_result = await WaitForResults(request_id, request_headers);
if (!json_result)
return null;
spinner.stop();
return json_result;
};
const TestSolution = async (state) => {
const result = await RunSolution(state, "Submitting Test", true);
if (result) {
const folder = state.variables["FOLDER"].value;
const problem_id = Number.parseInt(state.watchQuestion?.questionFrontendId);
const problem_title = state.watchQuestion?.titleSlug;
result.test_cases = generic.ReadProblemTestCases(folder, problem_id, problem_title);
}
return result;
};
exports.TestSolution = TestSolution;
const SubmitSolution = async (state) => {
const submission_result = await RunSolution(state, "Submitting Solution", false);
if (!submission_result)
return null;
const spinner = new pprint_1.Spinner(`Fetching submission Id ${submission_result.submission_id} details`);
spinner.start();
const f_cookies = formatter.FormatCookies(state.cookies);
const submission_details = await (0, exports.FetchSubmissionDetail)({ submissionId: submission_result?.submission_id }, { ...f_cookies });
spinner.stop();
return { ...submission_result, ...submission_details };
};
exports.SubmitSolution = SubmitSolution;