UNPKG

leetcode-fetcher-cli

Version:

A CLi Application for local fetching of leetcode problems

299 lines (298 loc) 14.7 kB
"use strict"; 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;