- // ==UserScript==
- // @name Leetcode solution's saver
- // @namespace http://tampermonkey.net/
- // @version 1.0.4
- // @description Script saves the file to a folder with corresponding difficulty, name and file extension
- // @author https://github.com/ruvn-1fgas
- // @match https://leetcode.com/*
- // @require https://code.jquery.com/jquery-latest.js
- // @require https://unpkg.com/turndown/dist/turndown.js
- // @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.com
- // @grant GM_download
- // @grant GM_xmlhttpRequest
- // @license MIT
-
- // ==/UserScript==
- /* globals jQuery, $, waitForKeyElements */
-
- const API_URL = 'https://leetcode.com/graphql/';
- const LANG_LIST = [{ 'name': 'C++', 'slug': 'cpp', 'id': 0, 'extension': '.cpp' }, { 'name': 'Java', 'slug': 'java', 'id': 1, 'extension': '.java' }, { 'name': 'Python', 'slug': 'python', 'id': 2, 'extension': '.py' }, { 'name': 'MySQL', 'slug': 'mysql', 'id': 3, 'extension': '.sql' }, { 'name': 'C', 'slug': 'c', 'id': 4, 'extension': '.c' }, { 'name': 'C#', 'slug': 'csharp', 'id': 5, 'extension': '.cs' }, { 'name': 'JavaScript', 'slug': 'javascript', 'id': 6, 'extension': '.js' }, { 'name': 'Ruby', 'slug': 'ruby', 'id': 7, 'extension': '.rb' }, { 'name': 'Bash', 'slug': 'bash', 'id': 8, 'extension': '.sh' }, { 'name': 'Swift', 'slug': 'swift', 'id': 9, 'extension': '.swift' }, { 'name': 'Go', 'slug': 'golang', 'id': 10, 'extension': '.go' }, { 'name': 'Python3', 'slug': 'python3', 'id': 11, 'extension': '.py' }, { 'name': 'Scala', 'slug': 'scala', 'id': 12, 'extension': '.scala' }, { 'name': 'Kotlin', 'slug': 'kotlin', 'id': 13, 'extension': '.kt' }, { 'name': 'MS SQL Server', 'slug': 'mssql', 'id': 14, 'extension': '.sql' }, { 'name': 'Oracle', 'slug': 'oraclesql', 'id': 15, 'extension': '.sql' }, { 'name': 'HTML', 'slug': 'html', 'id': 16, 'extension': '.html' }, { 'name': 'Python ML (beta)', 'slug': 'pythonml', 'id': 17, 'extension': '.py' }, { 'name': 'Rust', 'slug': 'rust', 'id': 18, 'extension': '.rs' }, { 'name': 'PHP', 'slug': 'php', 'id': 19, 'extension': '.php' }, { 'name': 'TypeScript', 'slug': 'typescript', 'id': 20, 'extension': '.ts' }, { 'name': 'Racket', 'slug': 'racket', 'id': 21, 'extension': '.rkt' }, { 'name': 'Erlang', 'slug': 'erlang', 'id': 22, 'extension': '.erl' }, { 'name': 'Elixir', 'slug': 'elixir', 'id': 23, 'extension': '.ex' }, { 'name': 'Dart', 'slug': 'dart', 'id': 24, 'extension': '.dart' }, { 'name': 'Python Data Science (beta)', 'slug': 'pythondata', 'id': 25, 'extension': '.py' }, { 'name': 'React', 'slug': 'react', 'id': 26, 'extension': '.js' }, { 'name': 'Vanilla JS', 'slug': 'vanillajs', 'id': 27, 'extension': '.js' }];
-
- let turndownService = new TurndownService();
- turndownService.addRule('pre', {
- filter: ['pre'],
- replacement: function (content) {
- return '\n```\n' + content + '```'
- }
- });
-
- (function (open) {
- XMLHttpRequest.prototype.open = function () {
- this.addEventListener("readystatechange", function () {
- if (this.readyState == 4 && window.location.href.search('/problems/') != -1) {
- main();
- }
- }, false);
- open.apply(this, arguments);
- };
- })(XMLHttpRequest.prototype.open);
-
- function main() {
- 'use strict'
- let buttons = $('.relative.ml-auto.flex.items-center.gap-3');
- if (buttons === undefined) {
- return;
- }
-
- if (buttons.children().length > 3) {
- return;
- }
-
- let downloadButton = createButton('Download', buttons.children().first().attr('class'), { 'margin-right': '4px' }, download);
- buttons.prepend(downloadButton);
- }
-
- function createButton(buttonName, buttonClass, buttonStyle, buttonClick) {
- let button = $('<button>' + buttonName + '</button>');
- button.addClass(buttonClass);
- button.removeClass('cursor-not-allowed');
- button.css(buttonStyle);
- button.click(buttonClick);
- return button;
- }
-
- async function download() {
- const taskInfo = await getTaskInfo();
- for (let key in taskInfo) {
- if (taskInfo[key] === undefined) {
- return;
- }
- }
- const taskDesc = await getTaskDesc(taskInfo);
- const taskCode = await getTaskCode(taskInfo);
- if (taskCode === undefined) {
- return;
- }
-
- save(taskInfo, taskCode, taskDesc);
- }
-
- async function fetchData(body, operationName) {
- const csrftoken = getCsrfToken();
- try {
- const response = await fetch(API_URL, {
- body,
- method: "POST",
- headers: {
- "x-csrftoken": csrftoken,
- "content-type": "application/json",
- }
- });
- const data = await response.json();
-
- return data.data[operationName];
- } catch (error) {
- console.log(error);
- }
- }
-
- async function getTaskInfo() {
- const slug = window.location.href.split('/')[4];
- let body = `{\"query\":\"\\n query questionTitle($titleSlug: String!) {\\n question(titleSlug: $titleSlug) {\\n questionId\\n questionFrontendId\\n title\\n titleSlug\\n isPaidOnly\\n difficulty\\n likes\\n dislikes\\n }\\n}\\n \",\"variables\":{\"titleSlug\":\"${slug}\"},\"operationName\":\"questionTitle\"}`
- const question = await fetchData(body, "question");
- const currentLanguageHolder = $('.flex.items-center').filter(function () {
- return $(this).attr('class') === 'flex items-center';
- });
- const childs = Array.from(currentLanguageHolder.children());
- const currentLanguage = LANG_LIST.find(({ name }) => childs.some(child => $(child).text().trim() === name));
- const { questionId, questionFrontendId, title, titleSlug, difficulty } = question;
- return {
- taskId: questionId,
- taskName: `${questionFrontendId}. ${title}`,
- titleSlug: titleSlug,
- level: difficulty,
- language: currentLanguage?.name,
- langId: currentLanguage?.id,
- fileExt: currentLanguage?.extension
- };
- }
-
- async function getTaskDesc(taskInfo) {
- const { titleSlug } = taskInfo;
- const body = `{\"query\":\"\\n query questionContent($titleSlug: String!) {\\n question(titleSlug: $titleSlug) {\\n content\\n mysqlSchemas\\n dataSchemas\\n }\\n}\\n \",\"variables\":{\"titleSlug\":\"${titleSlug}\"},\"operationName\":\"questionContent\"}`
-
- const { content } = await fetchData(body, "question");
- const markdown = turndownService.turndown(content);
- return markdown;
- }
-
- async function getTaskCode(taskInfo) {
- const { langId, taskId } = taskInfo;
- const body = `{\"query\":\"\\n query syncedCode($questionId: Int!, $lang: Int!) {\\n syncedCode(questionId: $questionId, lang: $lang) {\\n timestamp\\n code\\n }\\n}\\n \",\"variables\":{\"lang\":${taskInfo.langId},\"questionId\":${taskInfo.taskId}},\"operationName\":\"syncedCode\"}`;
-
- const { code } = await fetchData(body, "syncedCode");
- return code;
- }
-
- function getCsrfToken() {
- let csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken')).split('=')[1];
- return csrfToken;
- }
-
- async function saveCode(taskInfo, taskCode) {
- const { taskName, level, fileExt } = taskInfo;
- const filename = `Leetcode/${level}/${taskName}${fileExt}`;
- const bl = new Blob([taskCode], { type: `text/${fileExt}` });
- const download = {
- url: URL.createObjectURL(bl),
- name: filename
- };
- GM_download(download);
- }
-
- async function save(taskInfo, taskCode, taskDesc) {
- const { taskName, level, fileExt } = taskInfo;
- const codeFilename = `Leetcode/${level}/${taskName}${fileExt}`;
- // check if file exists
- let solvedTasks = localStorage.getItem('solved_tasks');
- solvedTasks = solvedTasks === null ? [] : solvedTasks.split(',');
- let isExists = solvedTasks === null ? false : solvedTasks.includes(`${taskName}${fileExt}`);
-
- if (!isExists) {
- const codeBl = new Blob([taskCode], { type: `text/${fileExt}` });
- const codeDownload = {
- url: URL.createObjectURL(codeBl),
- name: codeFilename
- };
- GM_download(codeDownload);
-
- if (solvedTasks === null) {
- solvedTasks = [];
- }
-
- solvedTasks.push(`${taskName}${fileExt}`);
-
- localStorage.setItem('solved_tasks', solvedTasks);
- }
-
- let solvedTasksDescs = localStorage.getItem('solved_tasks_descs');
- solvedTasksDescs = solvedTasksDescs === null ? [] : solvedTasksDescs.split(',');
- isExists = solvedTasksDescs === null ? false : solvedTasksDescs.includes(taskName);
-
- if (!isExists) {
- const descFilename = `Leetcode/${level}/${taskName}.md`;
- const descBl = new Blob([taskDesc], { type: 'text/md' });
- const descDownload = {
- url: URL.createObjectURL(descBl),
- name: descFilename
- };
- GM_download(descDownload);
-
- if (solvedTasksDescs === null) {
- solvedTasksDescs = [];
- }
-
- solvedTasksDescs.push(taskName);
-
- localStorage.setItem('solved_tasks_descs', solvedTasksDescs);
- }
- }