├── .gitignore ├── LICENSE ├── README.md ├── config-tmpl.json ├── cookie.png ├── fetchQuestion.js ├── fetchSubmission.js ├── mergeQA.js ├── package.json ├── utils.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | 61 | # Project specific excludes 62 | config.json 63 | problem 64 | submission 65 | leetcode.txt 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tay Yang Shun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Downloader 2 | 3 | Download your accepted submissions and questions from LeetCode! 4 | 5 | This repo refers to [yangshun/leetcode-downloader](https://github.com/yangshun/leetcode-downloader). 6 | 7 | ## Start 8 | 9 | ``` 10 | $ yarn 11 | $ cp config-tmpl.json config.json 12 | ``` 13 | 14 | Do the following steps to get your cookie at LeetCode 15 | 1. Open Chrome and login [leetcode.com](https://leetcode.com/). 16 | 2. Open developer tools and network tab. 17 | 3. Open one request sending to LeetCode and copy the cookie. 18 | 4. Paste it into `config.json`. 19 | 20 | ![GitHub Logo](./cookie.png) 21 | 22 | ``` 23 | # fetch the latest accepted submission of each question 24 | $ node fetchSubmission.js 25 | 26 | # fetch the description of each question 27 | $ node fetchQuestion.js 28 | 29 | # merge questions and accepted submissions 30 | $ node mergeQA.js 31 | ``` 32 | 33 | ## License 34 | 35 | MIT 36 | -------------------------------------------------------------------------------- /config-tmpl.json: -------------------------------------------------------------------------------- 1 | { 2 | "cookie": "", 3 | "SLEEP_TIME": 5000 4 | } 5 | -------------------------------------------------------------------------------- /cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noootown/leetcode-downloader/60f3d01312a6c4dc313ebc7170636e7e1fda489c/cookie.png -------------------------------------------------------------------------------- /fetchQuestion.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { 3 | numberPadZero, 4 | sleep, 5 | request, 6 | parseXpath 7 | } = require('./utils') 8 | const { 9 | SLEEP_TIME, 10 | } = require('./config') 11 | 12 | const dataPath = 'problem' 13 | 14 | if (!fs.existsSync(dataPath)) { 15 | fs.mkdirSync(dataPath) 16 | } 17 | 18 | ;(async () => { 19 | const problems = (await request( 20 | { 21 | url: 'https://leetcode.com/api/problems/all/', 22 | }, 23 | )).data.stat_status_pairs.map(({ 24 | stat: { 25 | question_id: id, 26 | question__title_slug: slug, 27 | }, 28 | }) => ({ id, slug })).sort((a, b) => a.id - b.id) 29 | 30 | for (let problem of problems) { 31 | const { id, slug } = problem 32 | let idStr = numberPadZero(id, 4) 33 | const filename = `${idStr}-${slug}.txt` 34 | 35 | console.log(`Downloading ${slug}`) 36 | 37 | const { data: questionData } = await request({ url: `https://leetcode.com/problems/${slug}` }) 38 | const questionText = parseXpath(questionData, 'head > meta[name="description"]', 'content') 39 | fs.writeFileSync(`${dataPath}/${filename}`, questionText) 40 | 41 | console.log(`Downloaded ${filename}`) 42 | 43 | await sleep(SLEEP_TIME) 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /fetchSubmission.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { 3 | numberPadZero, 4 | sleep, 5 | request, 6 | } = require('./utils') 7 | const { 8 | SLEEP_TIME, 9 | } = require('./config') 10 | 11 | const extMap = { 12 | bash: 'sh', 13 | c: 'c', 14 | cpp: 'cpp', 15 | csharp: 'cs', 16 | golang: 'go', 17 | java: 'java', 18 | javascript: 'js', 19 | mysql: 'sql', 20 | python: 'py', 21 | python3: 'py', 22 | ruby: 'rb', 23 | scala: 'scala', 24 | swift: 'swift', 25 | } 26 | 27 | const dataPath = 'submission' 28 | 29 | if (!fs.existsSync(dataPath)) { 30 | fs.mkdirSync(dataPath) 31 | } 32 | 33 | const solved = [] 34 | 35 | ;(async () => { 36 | const problems = (await request( 37 | { 38 | url: 'https://leetcode.com/api/problems/all/', 39 | }, 40 | )).data.stat_status_pairs.map(({ 41 | stat: { 42 | question_id: id, 43 | question__title_slug: slug, 44 | }, 45 | }) => ({ id, slug })).filter(({ id }) => solved.includes(id)).sort((a, b) => a.id - b.id) 46 | 47 | for (let problem of problems) { 48 | const { id, slug } = problem 49 | console.log(`Downloading ${slug}`) 50 | const { data: { submissions_dump } } = await request({ 51 | url: `https://leetcode.com/api/submissions/${slug}`, 52 | }) 53 | const acceptedSubmission = submissions_dump.filter(({ status_display }) => status_display === 'Accepted') 54 | if (acceptedSubmission.length === 0) { 55 | await sleep(SLEEP_TIME) 56 | continue 57 | } 58 | 59 | const { url, lang } = acceptedSubmission[0] 60 | 61 | await sleep(SLEEP_TIME) 62 | 63 | const { data: codeData } = await request({ 64 | url: `https://leetcode.com${url}`, 65 | }) 66 | 67 | let idStr = numberPadZero(id, 4) 68 | const matches = codeData.match(/submissionCode: '(.*)',\n editCodeUrl/) 69 | const code = `${ 70 | matches[1].replace(/\\u[\dA-F]{4}/gi, match => 71 | String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)) 72 | )}\n` 73 | const filename = `${idStr}-${slug}.${extMap[lang]}` 74 | fs.writeFileSync(`${dataPath}/${filename}`, code) 75 | console.log(`Downloaded ${filename}`) 76 | 77 | await sleep(SLEEP_TIME) 78 | } 79 | })() 80 | -------------------------------------------------------------------------------- /mergeQA.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | let output = '' 4 | 5 | const submissions = fs.readdirSync('submission') 6 | .map(filename => filename.match(/(\d+)([\w\d\-]+)\.\w+/)) 7 | 8 | submissions.forEach(submission => { 9 | const question = fs.readFileSync(`problem/${submission[1]}${submission[2]}.txt`) 10 | const solution = fs.readFileSync(`submission/${submission[0]}`) 11 | 12 | output += ` 13 | Question ${submission[1]}: 14 | ${question} 15 | 16 | ${solution} 17 | -------------------------------------------- 18 | 19 | ` 20 | }) 21 | 22 | output = output.replace(/\n{3,}/g, '\n') 23 | 24 | fs.writeFileSync(`leetcode.txt`, output) 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode-downloader", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "noootown", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^0.16.2", 13 | "cheerio": "^1.0.0-rc.2", 14 | "underscore.string": "^3.3.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const cheerio = require('cheerio') 3 | const s = require('underscore.string') 4 | const { 5 | cookie, 6 | } = require('./config.json') 7 | 8 | const numberPadZero = (number, zeroNum) => `${'0'.repeat(zeroNum - number.toString().length)}${number}`; 9 | 10 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) 11 | 12 | const request = (header) => axios.request( 13 | { 14 | ...{ 15 | method: 'get', 16 | headers: { 17 | Cookie: cookie, 18 | }, 19 | }, 20 | ...header, 21 | } 22 | ).catch(err => { 23 | console.log(err) 24 | }) 25 | 26 | const parseXpath = (xml, xpath, attr) => s.unescapeHTML(cheerio.load(xml)(xpath).attr(attr)) 27 | 28 | module.exports = { 29 | numberPadZero, 30 | sleep, 31 | request, 32 | parseXpath, 33 | } 34 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@*": 6 | version "10.11.7" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.7.tgz#0e75ca9357d646ca754016ca1d68a127ad7e7300" 8 | 9 | axios@^0.16.2: 10 | version "0.16.2" 11 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" 12 | dependencies: 13 | follow-redirects "^1.2.3" 14 | is-buffer "^1.1.5" 15 | 16 | boolbase@~1.0.0: 17 | version "1.0.0" 18 | resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 19 | 20 | cheerio@^1.0.0-rc.2: 21 | version "1.0.0-rc.2" 22 | resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" 23 | dependencies: 24 | css-select "~1.2.0" 25 | dom-serializer "~0.1.0" 26 | entities "~1.1.1" 27 | htmlparser2 "^3.9.1" 28 | lodash "^4.15.0" 29 | parse5 "^3.0.1" 30 | 31 | core-util-is@~1.0.0: 32 | version "1.0.2" 33 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 34 | 35 | css-select@~1.2.0: 36 | version "1.2.0" 37 | resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" 38 | dependencies: 39 | boolbase "~1.0.0" 40 | css-what "2.1" 41 | domutils "1.5.1" 42 | nth-check "~1.0.1" 43 | 44 | css-what@2.1: 45 | version "2.1.0" 46 | resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" 47 | 48 | debug@=3.1.0: 49 | version "3.1.0" 50 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 51 | dependencies: 52 | ms "2.0.0" 53 | 54 | dom-serializer@0, dom-serializer@~0.1.0: 55 | version "0.1.0" 56 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" 57 | dependencies: 58 | domelementtype "~1.1.1" 59 | entities "~1.1.1" 60 | 61 | domelementtype@1, domelementtype@^1.3.0: 62 | version "1.3.0" 63 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" 64 | 65 | domelementtype@~1.1.1: 66 | version "1.1.3" 67 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" 68 | 69 | domhandler@^2.3.0: 70 | version "2.4.2" 71 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" 72 | dependencies: 73 | domelementtype "1" 74 | 75 | domutils@1.5.1: 76 | version "1.5.1" 77 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" 78 | dependencies: 79 | dom-serializer "0" 80 | domelementtype "1" 81 | 82 | domutils@^1.5.1: 83 | version "1.7.0" 84 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" 85 | dependencies: 86 | dom-serializer "0" 87 | domelementtype "1" 88 | 89 | entities@^1.1.1, entities@~1.1.1: 90 | version "1.1.1" 91 | resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" 92 | 93 | follow-redirects@^1.2.3: 94 | version "1.5.9" 95 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6" 96 | dependencies: 97 | debug "=3.1.0" 98 | 99 | htmlparser2@^3.9.1: 100 | version "3.9.2" 101 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" 102 | dependencies: 103 | domelementtype "^1.3.0" 104 | domhandler "^2.3.0" 105 | domutils "^1.5.1" 106 | entities "^1.1.1" 107 | inherits "^2.0.1" 108 | readable-stream "^2.0.2" 109 | 110 | inherits@^2.0.1, inherits@~2.0.3: 111 | version "2.0.3" 112 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 113 | 114 | is-buffer@^1.1.5: 115 | version "1.1.6" 116 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 117 | 118 | isarray@~1.0.0: 119 | version "1.0.0" 120 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 121 | 122 | lodash@^4.15.0: 123 | version "4.17.11" 124 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" 125 | 126 | ms@2.0.0: 127 | version "2.0.0" 128 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 129 | 130 | nth-check@~1.0.1: 131 | version "1.0.1" 132 | resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" 133 | dependencies: 134 | boolbase "~1.0.0" 135 | 136 | parse5@^3.0.1: 137 | version "3.0.3" 138 | resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" 139 | dependencies: 140 | "@types/node" "*" 141 | 142 | process-nextick-args@~2.0.0: 143 | version "2.0.0" 144 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 145 | 146 | readable-stream@^2.0.2: 147 | version "2.3.6" 148 | resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 149 | dependencies: 150 | core-util-is "~1.0.0" 151 | inherits "~2.0.3" 152 | isarray "~1.0.0" 153 | process-nextick-args "~2.0.0" 154 | safe-buffer "~5.1.1" 155 | string_decoder "~1.1.1" 156 | util-deprecate "~1.0.1" 157 | 158 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 159 | version "5.1.2" 160 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 161 | 162 | sprintf-js@^1.0.3: 163 | version "1.1.1" 164 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" 165 | 166 | string_decoder@~1.1.1: 167 | version "1.1.1" 168 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 169 | dependencies: 170 | safe-buffer "~5.1.0" 171 | 172 | underscore.string@^3.3.5: 173 | version "3.3.5" 174 | resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" 175 | dependencies: 176 | sprintf-js "^1.0.3" 177 | util-deprecate "^1.0.2" 178 | 179 | util-deprecate@^1.0.2, util-deprecate@~1.0.1: 180 | version "1.0.2" 181 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 182 | --------------------------------------------------------------------------------