├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ └── submit.yml ├── .gitignore ├── .prettierrc.mjs ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── icon.png ├── background.ts ├── components ├── BackIcon.tsx ├── Checkbox.tsx ├── Dropdown.tsx ├── FailImage.tsx ├── HyperTortureMode.tsx ├── Input.tsx ├── NoPermissions.tsx ├── SettingDrawer.tsx ├── SettingLabel.tsx └── SettingsIcon.tsx ├── constants.ts ├── contents ├── CongratulationsModal.tsx ├── FailedModal.tsx └── modal.css ├── jest.config.cjs ├── leetcode-problems ├── allProblems.json ├── blind75Problems.json ├── metaTop100.json └── neetCode150Problems.json ├── leetcodeProblems.ts ├── package.json ├── pnpm-lock.yaml ├── popup.tsx ├── storage.ts ├── styles.css ├── testDirectory └── testFile.json ├── tests ├── 01_savetoJSON.test.js └── 02_scraper.test.js ├── tsconfig.json ├── types.ts └── web_scraper ├── run_web_scraper.js └── web_scrape_problems.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["plugin:react/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["react", "@typescript-eslint"], 13 | "rules": { 14 | "react/prop-types": "off", 15 | "react/react-in-jsx-scope": "off", 16 | "react/no-unescaped-entities": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/submit.yml: -------------------------------------------------------------------------------- 1 | name: "Submit to Web Store" 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Cache pnpm modules 11 | uses: actions/cache@v3 12 | with: 13 | path: ~/.pnpm-store 14 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 15 | restore-keys: | 16 | ${{ runner.os }}- 17 | - uses: pnpm/action-setup@v2.2.4 18 | with: 19 | version: latest 20 | run_install: true 21 | - name: Use Node.js 16.x 22 | uses: actions/setup-node@v3.4.1 23 | with: 24 | node-version: 16.x 25 | cache: "pnpm" 26 | - name: Build the extension 27 | run: pnpm build 28 | - name: Package the extension into a zip artifact 29 | run: pnpm package 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.SUBMIT_KEYS }} 34 | artifact: build/chrome-mv3-prod.zip 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | 16 | # debug 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | .pnpm-debug.log* 21 | 22 | # local env files 23 | .env*.local 24 | 25 | out/ 26 | build/ 27 | dist/ 28 | 29 | # plasmo 30 | .plasmo 31 | keys.json 32 | # typescript 33 | .tsbuildinfo 34 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | export default { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: ["@ianvs/prettier-plugin-sort-imports"], 14 | importOrder: [ 15 | "", // Node.js built-in modules 16 | "", // Imports not matched by other special words or groups. 17 | "", // Empty line 18 | "^@plasmo/(.*)$", 19 | "", 20 | "^@plasmohq/(.*)$", 21 | "", 22 | "^~(.*)$", 23 | "", 24 | "^[./]" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to LeetCode Torture 2 | 3 | First off, thanks for taking the time to contribute to this torture machine :tada: 4 | 5 | ## How Can I Contribute? 6 | 7 | ### Reporting Bugs 8 | 9 | 1. **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues). 10 | 11 | 2. If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/new). 12 | 13 | ### Suggesting Enhancements 14 | 15 | 1. Create a feature request issue detailing what you'd like to see implemented. 16 | 17 | ### Code Contribution 18 | 19 | 1. Fork the repo on GitHub. 20 | 2. Clone the forked repository. 21 | 3. Create a new feature branch based on `main`. 22 | 4. Make your changes. 23 | 5. Ensure the code adheres to the existing style. 24 | 6. Submit a pull request against the `main` branch of the upstream repository. 25 | 26 | ## Coding Conventions 27 | 28 | * We primarily use JavaScript for this project. 29 | * Follow modern JavaScript best practices. 30 | * Code comments are appreciated. 31 | 32 | ## Commit Messages 33 | 34 | Write clear and meaningful Git commit messages (they can be funny, but make it clear too) 35 | It's not ideal to have something like "Increased torture by 500%", but you could say "Added more hard problems, increased torture by 500%" 36 | Just need to know what you did 37 | 38 | ## Pull Requests 39 | 40 | Ensure that your PRs are small, focused, and you clearly explain the changes made in the description. 41 | 42 | ## License 43 | 44 | By contributing to this project, you agree that your contributions will be licensed under the same terms used in the project. 45 | 46 | ## Questions? 47 | 48 | Feel free to contact the project maintainers if you have any questions or concerns. 49 | 50 | Happy Coding! :sparkles: 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 The Coding Sloth 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 | This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). 2 | 3 | # [LeetCode Torture](https://chromewebstore.google.com/detail/leetcode-torture/clbhgfneekiimoaakhhdjimgnnbnfbeh) 4 | 5 | ![LeetCode Torture Logo](https://raw.githubusercontent.com/The-CodingSloth/haha-funny-leetcode-extension/main/assets/icon.png) 6 | 7 | LeetCode Torture is an extension designed to make you productive to ace those technical interviews. When you activate this extension, you won't be able to access any websites except LeetCode until you solve your randomly assigned LeetCode problem. [Here's the video on YouTube.](https://youtu.be/e4ReFOWMG9o?si=CJ2EdqVPFPdcc7GN) 8 | 9 | ## Features 10 | 11 | - Random Problem Assignment: Get a random LeetCode problem every day 12 | - Website Blocker: Prevents you from accessing any websites until you solve your problem 13 | 14 | ## Local Installation for Testing and Development 15 | 16 | ### Setting up: 17 | 18 | - Have Node.js Installed on your machine 19 | - Clone or download the repo 20 | - Navigate to the root directory 21 | - Install pnpm if you don't have it (it's recommended to use it from plasmo) 22 | - Run `pnpm install` to install the required dependencies 23 | 24 | ### Building the Extension to run locally on Chrome: 25 | 26 | - In the terminal run the command `pnpm dev` or `npm run dev` 27 | - Open Chrome and open up your extensions page 28 | - Enable Developer Mode using the toggle at the top right of the page 29 | - Click on the Load unpacked button on the top left of the page 30 | - Locate and select the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. 31 | - The extension should now be installed have fun 32 | - You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. 33 | 34 | ### For Firefox development 35 | 36 | - Make sure your Firefox is on version 109 (we're using manifest v3) 37 | - In the terminal run the command `pnpm dev --target=firefox-mv3` or `npm run dev-firefox` 38 | - In the build folder you should now see `build/firefox-mv3-dev` 39 | - In Firefox type about:debugging in the URL. 40 | - Go to "This Firefox" 41 | - In Temporary Extensions click "Load Temporary Add-on" 42 | - Select any file from your folder 43 | 44 | #### To get the extension working properly right now on Firefox 45 | 46 | - Go to leetcode.com and right click the extension, and give the extension permission 47 | - Do the same for any websites you visit (Firefox, doesn't give permissions automatically like chrome which is why we have to do this, it's annoying) 48 | 49 | ## FAQ 50 | 51 | ### Why did you make this? 52 | 53 | Just for fun 54 | 55 | ### Do I need to be a senior 10x developer to help and contribute? 56 | 57 | Nope, anybody can contribute to this torture machine, skill level does not matter. I made this project with no knowledge about making extensions 58 | 59 | ### How do I help and improve LeetCode Torture? 60 | 61 | Read our [CONTRIBUTING.md file](https://github.com/The-CodingSloth/haha-funny-leetcode-extension/blob/main/CONTRIBUTING.md) to learn how to help make this extension more painful for everyone 62 | 63 | ### I don't even know where to start 64 | 65 | That's perfectly fine, take some time and read the code to understand what's going on. You don't have to make super big changes, they can be as small as fixing my dumb typos on these files or adding helpful comments to the code. 66 | 67 | For further guidance, [visit our Documentation](https://docs.plasmo.com/) 68 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-CodingSloth/haha-funny-leetcode-extension/2a0cf9e81226734b75ec1e2968866120232c9843/assets/icon.png -------------------------------------------------------------------------------- /background.ts: -------------------------------------------------------------------------------- 1 | import { getHyperTortureMode, resetHyperTortureStreak, storage } from "storage" 2 | 3 | import { 4 | getAllLeetCodeProblems, 5 | getLeetCodeProblemFromProblemSet 6 | } from "~leetcodeProblems" 7 | import type { APILeetCodeProblem, UserState } from "~types" 8 | 9 | const LEETCODE_URL = "https://leetcode.com" 10 | const RULE_ID = 1 11 | const isLeetCodeUrl = (url: string) => url.includes(LEETCODE_URL) 12 | 13 | const isSubmissionSuccessURL = (url: string) => 14 | url.includes("/submissions/detail/") && url.includes("/check/") 15 | 16 | const sendUserSolvedMessage = (languageUsed: string) => { 17 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 18 | chrome.tabs.sendMessage(tabs[0].id, { 19 | action: "userSolvedProblem", 20 | language: languageUsed 21 | }) 22 | }) 23 | } 24 | 25 | const sendUserFailedMessage = () => { 26 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 27 | chrome.tabs.sendMessage(tabs[0].id, { 28 | action: "userFailedProblem" 29 | }) 30 | }) 31 | } 32 | 33 | const state: UserState = { 34 | leetcodeProblemSolved: false, 35 | leetCodeProblem: { 36 | url: null, 37 | name: null 38 | }, 39 | lastSubmissionDate: new Date(0), 40 | solvedListenerActive: false, 41 | lastAttemptedUrl: null, 42 | urlListener: null, 43 | includePremium: null, 44 | hyperTortureMode: null, 45 | HTcurrentStreak: null 46 | } 47 | 48 | export async function getProblemListFromLeetCodeAPI( 49 | difficulty: string, 50 | problemSet: string 51 | ): Promise { 52 | try { 53 | const query = ` 54 | query problemsetQuestionList { 55 | problemsetQuestionList: questionList( 56 | categorySlug: "" 57 | limit: -1 58 | skip: 0 59 | filters: { 60 | ${ 61 | difficulty && difficulty !== "all" 62 | ? "difficulty: " + difficulty 63 | : "" 64 | } 65 | ${problemSet?.length ? "listId: " + '"' + problemSet + '"' : ""} 66 | } 67 | ) { 68 | questions: data { 69 | acRate 70 | difficulty 71 | freqBar 72 | frontendQuestionId: questionFrontendId 73 | isFavor 74 | paidOnly: isPaidOnly 75 | status 76 | title 77 | titleSlug 78 | topicTags { 79 | name 80 | id 81 | slug 82 | } 83 | hasSolution 84 | hasVideoSolution 85 | } 86 | } 87 | } 88 | ` 89 | 90 | const body = { 91 | query 92 | } 93 | 94 | const response = await fetch("https://leetcode.com/graphql", { 95 | method: "POST", 96 | body: JSON.stringify(body), 97 | headers: { 98 | "Content-Type": "application/json" 99 | } 100 | }) 101 | 102 | const responseData = await response.json() 103 | await storage.updatePermissions(true) 104 | 105 | return responseData.data.problemsetQuestionList.questions 106 | } catch (error) { 107 | console.log(error.toString()) 108 | if ( 109 | error.message.includes("NetworkError") || 110 | error.message.includes("CORS") || 111 | error.message === "Network response was not ok" 112 | ) { 113 | console.log("CORS error detected.") 114 | await storage.updatePermissions(false) 115 | } 116 | return undefined 117 | } 118 | } 119 | 120 | export const handleAdditionalProblemRedirect = async (problemUrl: string) => { 121 | if (await storage.getEnableRedirectOnEveryProblem()) 122 | await setRedirectRule(problemUrl) 123 | } 124 | 125 | export async function generateRandomLeetCodeProblem(): Promise<{ 126 | url: string 127 | name: string 128 | }> { 129 | try { 130 | const problemSet = await storage.getProblemSet() 131 | const difficulty = await storage.getDifficulty() 132 | // Check if list is from Leetcode Graphql or all 133 | if (problemSet === "all" || problemSet.startsWith("lg")) { 134 | await storage.initiateLoading() 135 | return await getAllLeetCodeProblems(difficulty, problemSet) 136 | } else { 137 | return await getLeetCodeProblemFromProblemSet(difficulty, problemSet) 138 | } 139 | } catch (error) { 140 | console.error("Error generating random problem", error) 141 | return undefined 142 | } finally { 143 | await storage.stopLoading() 144 | } 145 | } 146 | 147 | function onMessageReceived( 148 | message: any, 149 | sender: chrome.runtime.MessageSender, 150 | sendResponse: (response?: any) => void 151 | ): void { 152 | switch (message.action) { 153 | case "fetchingProblem": 154 | // Handle the start of the problem fetch. 155 | // Currently, we'll just log it for clarity, but you can add other logic here if needed. 156 | console.log("Fetching problem started.") 157 | break 158 | case "problemFetched": 159 | // Handle the end of the problem fetch. 160 | console.log("Fetching problem completed.") 161 | break 162 | case "getProblemStatus": 163 | sendResponse({ 164 | problemSolved: state.leetcodeProblemSolved, 165 | problem: state.leetCodeProblem 166 | }) 167 | case "userClickedSubmit": 168 | state.lastSubmissionDate = new Date() 169 | state.solvedListenerActive = true 170 | console.log( 171 | "User clicked submit, adding listener", 172 | state.solvedListenerActive 173 | ) 174 | chrome.webRequest.onCompleted.addListener(checkIfUserSolvedProblem, { 175 | urls: ["*://leetcode.com/submissions/detail/*/check/"] 176 | }) 177 | break 178 | default: 179 | console.warn("Unknown message action:", message.action) 180 | } 181 | } 182 | 183 | async function setRedirectRule(redirectUrl: string) { 184 | const redirectRule = { 185 | id: RULE_ID, 186 | priority: 1, 187 | action: { 188 | type: "redirect", 189 | redirect: { url: redirectUrl } 190 | }, 191 | condition: { 192 | urlFilter: "*://*/*", 193 | excludedInitiatorDomains: [ 194 | "leetcode.com", 195 | "www.leetcode.com", 196 | "developer.chrome.com" 197 | ], 198 | 199 | resourceTypes: ["main_frame"] 200 | } 201 | } 202 | 203 | try { 204 | chrome.declarativeNetRequest.updateDynamicRules({ 205 | removeRuleIds: [RULE_ID], 206 | addRules: [redirectRule as chrome.declarativeNetRequest.Rule] 207 | }) 208 | console.log("Redirect rule updated") 209 | } catch (error) { 210 | console.error("Error updating redirect rule:", error) 211 | } 212 | } 213 | 214 | export const updateProblemState = async (problem: { 215 | name: string 216 | url: string 217 | }) => { 218 | await storage.updateProblem(problem, state.leetcodeProblemSolved) 219 | } 220 | 221 | export const updateStorage = async () => { 222 | try { 223 | const isRedirectEnabled = await storage.getEnableRedirectOnEveryProblem() 224 | let problem = await generateRandomLeetCodeProblem() 225 | state.leetcodeProblemSolved = false 226 | updateProblemState(problem) 227 | if (!state.leetcodeProblemSolved && isRedirectEnabled) 228 | await setRedirectRule(problem.url) 229 | } catch (error) { 230 | throw new Error("Error generating random problem: " + error) 231 | } 232 | } 233 | 234 | const checkIfUserSolvedProblem = async ( 235 | details: chrome.webRequest.WebResponseCacheDetails 236 | ) => { 237 | // If the user has already solved the problem, then don't do anything 238 | if (await storage.getProblemSolved()) return 239 | // Get the current active tab's URL 240 | let currentURL = "" 241 | try { 242 | const [activeTab] = await new Promise((resolve) => { 243 | chrome.tabs.query({ active: true, currentWindow: true }, resolve) 244 | }) 245 | 246 | currentURL = activeTab.url 247 | } catch (error) { 248 | console.error("Error getting active tab:", error) 249 | return 250 | } 251 | 252 | const problemUrl = await storage.getProblemUrl() 253 | 254 | const sameUrl = 255 | problemUrl === currentURL || problemUrl + "description/" === currentURL 256 | 257 | if (!sameUrl) { 258 | return 259 | } 260 | 261 | //lastCheckedUrl = details.url 262 | //lastCheckedTimestamp = now 263 | 264 | if (state.solvedListenerActive) { 265 | // Remove the listener so that it doesn't fire again, since the outcome will either be success or fail 266 | // And we'll add it again when the user clicks submit 267 | state.solvedListenerActive = false 268 | chrome.webRequest.onCompleted.removeListener(checkIfUserSolvedProblem) 269 | } 270 | 271 | if (isSubmissionSuccessURL(details.url)) { 272 | try { 273 | const hyperTortureMode = await getHyperTortureMode() 274 | const response = await fetch(details.url) 275 | const data = await response.json() 276 | if (data.state === "STARTED" || data.state === "PENDING") { 277 | if (!state.solvedListenerActive) { 278 | state.solvedListenerActive = true 279 | chrome.webRequest.onCompleted.addListener(checkIfUserSolvedProblem, { 280 | urls: ["*://leetcode.com/submissions/detail/*/check/"] 281 | }) 282 | } 283 | return 284 | } 285 | if (data.status_msg !== "Accepted") { 286 | if (hyperTortureMode) { 287 | await resetHyperTortureStreak() 288 | sendUserFailedMessage() 289 | } 290 | return 291 | } 292 | if ( 293 | data.status_msg === "Accepted" && 294 | data.state === "SUCCESS" && 295 | !data.code_answer 296 | ) { 297 | await storage.updateStreak() 298 | state.leetcodeProblemSolved = true 299 | chrome.declarativeNetRequest.updateDynamicRules({ 300 | removeRuleIds: [RULE_ID] 301 | }) 302 | chrome.webRequest.onCompleted.removeListener(checkIfUserSolvedProblem) 303 | if (hyperTortureMode) { 304 | console.log("Hyper torture mode is enabled") 305 | if (state.lastAttemptedUrl) { 306 | chrome.tabs.update({ url: state.lastAttemptedUrl }) 307 | } 308 | await updateStorage() 309 | } else { 310 | sendUserSolvedMessage(data?.lang) 311 | } 312 | } 313 | } catch (error) { 314 | console.error("Error:", error) 315 | } 316 | } 317 | } 318 | 319 | async function tryResetStreak() { 320 | const lastCompletion = await storage.getLastCompletion() 321 | const yesterday = new Date().getDate() - 1 322 | if (lastCompletion.getDate() < yesterday) { 323 | await storage.resetStreak() 324 | return true 325 | } 326 | return false 327 | } 328 | 329 | export async function toggleUrlListener(toggle: boolean): Promise { 330 | if (toggle) { 331 | // Save users request url for further redirect 332 | // Save users request url for further redirect 333 | state.urlListener = (details: chrome.webRequest.WebRequestBodyDetails) => { 334 | if ( 335 | !isLeetCodeUrl(details.url) && 336 | details.type === "main_frame" && 337 | !details.url.includes("chrome-extension:") 338 | ) { 339 | state.lastAttemptedUrl = details.url 340 | } 341 | return null // return null when no BlockingResponse is needed 342 | } 343 | 344 | chrome.webRequest.onBeforeRequest.addListener(state.urlListener, { 345 | urls: [""] 346 | }) 347 | } else { 348 | chrome.webRequest.onBeforeRequest.removeListener(state.urlListener) 349 | } 350 | } 351 | export async function handleRedirectRule() { 352 | const isRedirectEnabled = await storage.getEnableRedirectOnEveryProblem() 353 | if (isRedirectEnabled) { 354 | const problemUrl = await storage.getProblemUrl() 355 | await setRedirectRule(problemUrl) 356 | } else { 357 | try { 358 | chrome.declarativeNetRequest.updateDynamicRules({ 359 | removeRuleIds: [RULE_ID] 360 | }) 361 | console.log("Redirect rule removed") 362 | } catch (error) { 363 | console.error("Error removing redirect rule:", error) 364 | } 365 | } 366 | } 367 | const getMsUntilMidnight = () => { 368 | const currentTime = Date.now() 369 | const midnight = new Date() 370 | midnight.setHours(24, 0, 0, 0) 371 | return midnight.getTime() - currentTime 372 | } 373 | 374 | chrome.runtime.onInstalled.addListener(async () => { 375 | await updateStorage() 376 | await tryResetStreak() 377 | await toggleUrlListener(await getHyperTortureMode()) 378 | }) 379 | 380 | chrome.alarms.get("midnightAlarm", (alarm) => { 381 | if (alarm) return 382 | const msUntilMidnight = getMsUntilMidnight() 383 | const oneDayInMinutes = 60 * 24 384 | chrome.alarms.create("midnightAlarm", { 385 | when: Date.now() + msUntilMidnight, 386 | periodInMinutes: oneDayInMinutes 387 | }) 388 | }) 389 | 390 | chrome.alarms.onAlarm.addListener(async () => { 391 | await updateStorage() 392 | await tryResetStreak() 393 | }) 394 | 395 | // Need to add these listeners to global scope so that when the workers become inactive, they are set again 396 | chrome.runtime.onMessage.addListener(onMessageReceived) 397 | -------------------------------------------------------------------------------- /components/BackIcon.tsx: -------------------------------------------------------------------------------- 1 | const BackIcon = () => { 2 | return ( 3 | 4 | ) 5 | } 6 | 7 | export default BackIcon 8 | -------------------------------------------------------------------------------- /components/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | const Checkbox = ({handleChange, name, checked}) => { 2 | return ( 3 | 4 | ) 5 | } 6 | 7 | export default Checkbox 8 | -------------------------------------------------------------------------------- /components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | const Dropdown = ({ options, defaultValue, handleChange, name }) => { 4 | const [value, setValue] = useState(defaultValue || "") 5 | const onChange = (e) => { 6 | setValue(e.target.value) 7 | handleChange(e) 8 | } 9 | useEffect(() => { 10 | setValue(defaultValue) 11 | }, [defaultValue]) 12 | return ( 13 | 25 | ) 26 | } 27 | 28 | export default Dropdown 29 | -------------------------------------------------------------------------------- /components/FailImage.tsx: -------------------------------------------------------------------------------- 1 | const FailImage = () => { 2 | return ( 3 | fail 7 | ) 8 | } 9 | 10 | export default FailImage 11 | -------------------------------------------------------------------------------- /components/HyperTortureMode.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const HyperTortureMode = ({ message, problemName }): React.JSX.Element => { 4 | const [noEscapeMessage, setNoEscapeMessage] = useState("") 5 | return ( 6 | <> 7 |

❗Hyper 🤓 Torture mode active❗

8 | 9 |

{message}

10 | 11 |
12 |

{problemName}

13 | 18 |
19 | 20 | ) 21 | } 22 | 23 | export default HyperTortureMode 24 | -------------------------------------------------------------------------------- /components/Input.tsx: -------------------------------------------------------------------------------- 1 | const Input = ({type, handleChange, name}) => { 2 | return ( 3 | 4 | ) 5 | } 6 | 7 | export default Input 8 | -------------------------------------------------------------------------------- /components/NoPermissions.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | const NoPermissions = ({ permissionsEnabled }) => { 4 | const [checkingPermissions, setCheckingPermissions] = useState(true) 5 | const isFirefox = navigator.userAgent.includes("Firefox") 6 | 7 | useEffect(() => { 8 | const checkPermissions = async () => { 9 | try { 10 | setCheckingPermissions(true) 11 | } catch (e) { 12 | console.error(e) 13 | } finally { 14 | setCheckingPermissions(false) 15 | } 16 | } 17 | 18 | !permissionsEnabled && checkPermissions() 19 | }, [permissionsEnabled]) 20 | 21 | return ( 22 |
23 |
24 | {checkingPermissions && ( 25 |
26 |

Please wait while we check permissions

27 | 28 |
29 | )} 30 | {!checkingPermissions && ( 31 | <> 32 |

Permissions are not enabled for this extension.

33 | 34 | {isFirefox && ( 35 | <> 36 |

37 | To do this, right click the extension and select manage 38 | extension. 39 |

40 |

41 | Then navigate to the permissions tab and enable the optional 42 | permissions. 43 |

44 |

45 | It will say "Access your data for all websites" but it is only 46 | used for redirecting you to the problem page and getting the 47 | leetcode problem. I Promise. 48 |

49 | 50 | )} 51 | {!isFirefox && ( 52 |

53 | Please navigate to the extensions page of your browser, locate 54 | this extension, and enable the required permissions. 55 |

56 | )} 57 | 58 | )} 59 |
60 |
61 | ) 62 | } 63 | 64 | export default NoPermissions 65 | -------------------------------------------------------------------------------- /components/SettingDrawer.tsx: -------------------------------------------------------------------------------- 1 | import { useStorage } from "@plasmohq/storage/hook" 2 | 3 | import { 4 | handleRedirectRule, 5 | toggleUrlListener, 6 | updateStorage 7 | } from "~background" 8 | 9 | import BackIcon from "./BackIcon" 10 | import SettingLabel from "./SettingLabel" 11 | 12 | const SettingDrawer = ({ close, setClose }) => { 13 | const [problemSets, setProblemSets] = useStorage("problemSets") 14 | const [difficulty, setDifficulty] = useStorage("difficulty") 15 | const [leetcodeProblemSolved] = useStorage("leetCodeProblemSolved") 16 | const [includePremium, setIncludePremium] = 17 | useStorage("includePremium") 18 | const [enableRedirect, setEnableRedirect] = useStorage( 19 | "enableRedirectOnEveryProblem" 20 | ) 21 | const [hyperTortureMode, setHyperTortureMode] = 22 | useStorage("hyperTortureMode") 23 | const [_, setHTcurrentStreak] = useStorage("HT_currentStreak") 24 | const settingList = [ 25 | { 26 | name: "Disable the torture", 27 | description: "Click to disable/enable redirects", 28 | checkboxProps: { 29 | checked: enableRedirect ?? false, 30 | handleChange: async (e) => { 31 | setEnableRedirect(e.target.checked) 32 | 33 | handleRedirectRule() 34 | } 35 | } 36 | }, 37 | { 38 | name: "Problem Sets", 39 | description: "Choose the leetcode problems you'd like", 40 | dropdownProps: { 41 | options: { 42 | all: "All Leetcode Problems", 43 | allNeetcode: "All Neetcode Problems", 44 | NeetCode150: "Neetcode 150", 45 | Blind75: "Blind 75", 46 | metaTop100: "Meta Top 100", 47 | "lg-5htp6xyg": "LeetCode Curated SQL 70", 48 | "lg-79h8rn6": "Top 100 Liked Questions", 49 | "lg-wpwgkgt": "Top Interview Questions", 50 | "lg-o9exaktc": "Tayomide's Questions" 51 | }, 52 | defaultValue: problemSets, 53 | handleChange: async (e) => { 54 | setProblemSets(e.target.value) 55 | 56 | !leetcodeProblemSolved ? await updateStorage() : null 57 | } 58 | } 59 | }, 60 | { 61 | name: "Problem Difficulty", 62 | description: "Choose the leetcode difficulty you'd like", 63 | dropdownProps: { 64 | options: { 65 | all: "All difficulties", 66 | EASY: "Easy", 67 | MEDIUM: "Medium", 68 | HARD: "Hard" 69 | }, 70 | defaultValue: difficulty, 71 | handleChange: async (e) => { 72 | setDifficulty(e.target.value) 73 | !leetcodeProblemSolved ? await updateStorage() : null 74 | } 75 | } 76 | }, 77 | { 78 | name: "Include Premium Problems", 79 | description: "Toggle whether to include premium problems", 80 | checkboxProps: { 81 | checked: includePremium ?? false, 82 | handleChange: async (e) => { 83 | setIncludePremium(e.target.checked) 84 | !leetcodeProblemSolved ? await updateStorage() : null 85 | } 86 | } 87 | }, 88 | 89 | { 90 | name: 'Enable "Hyper Torture" mode 🤓', 91 | description: 92 | "Toggle mode that forces you to solve a problem every time you open a new page", 93 | checkboxProps: { 94 | checked: hyperTortureMode ?? false, 95 | handleChange: async (e) => { 96 | setHTcurrentStreak(0) 97 | setHyperTortureMode(e.target.checked) 98 | await updateStorage() 99 | await toggleUrlListener(e.target.checked) 100 | } 101 | } 102 | } 103 | /* TODO: Add this feature later 104 | { 105 | name: "Number of problems to solve", 106 | description: 107 | "Choose the amount of leetcode problems you'd like to solve before being able to access websites", 108 | inputProps: { 109 | type: "number", 110 | handleChange: (e) => {} 111 | } 112 | } 113 | */ 114 | ] 115 | return ( 116 |
117 | 123 |
    124 | {leetcodeProblemSolved && !hyperTortureMode && ( 125 |

    126 | Congrats you solved your problem today, these settings will be 127 | applied tomorrow 128 |

    129 | )} 130 | {settingList.map((settingProps, key) => ( 131 |
  • 132 | 133 |
  • 134 | ))} 135 |
136 |
137 | ) 138 | } 139 | 140 | export default SettingDrawer 141 | -------------------------------------------------------------------------------- /components/SettingLabel.tsx: -------------------------------------------------------------------------------- 1 | import Checkbox from "./Checkbox"; 2 | import Dropdown from "./Dropdown" 3 | import Input from "./Input" 4 | 5 | const SettingLabel = ({description, name, inputProps, dropdownProps, checkboxProps} : { 6 | description: any; 7 | name: any; 8 | inputProps?: any; 9 | dropdownProps?: any; 10 | checkboxProps?: any; 11 | }) => { 12 | return ( 13 | 24 | ) 25 | } 26 | 27 | export default SettingLabel 28 | -------------------------------------------------------------------------------- /components/SettingsIcon.tsx: -------------------------------------------------------------------------------- 1 | const SettingsIcon = () => { 2 | return ( 3 | 4 | 5 | 6 | ) 7 | } 8 | 9 | export default SettingsIcon 10 | -------------------------------------------------------------------------------- /constants.ts: -------------------------------------------------------------------------------- 1 | export const messages = { 2 | cpp: { 3 | language: "cpp", 4 | message: 5 | "You think you are better than everyone just because your language has pointers?" 6 | }, 7 | java: { 8 | language: "java", 9 | message: "Just use C++ like everyone else." 10 | }, 11 | javascript: { 12 | language: "javascript", 13 | message: "Don't you have to download a library to do that?" 14 | }, 15 | rust: { 16 | language: "rust", 17 | message: "Why would you use rust for leetcode?" 18 | }, 19 | c: { 20 | language: "c", 21 | message: "You know C++ exists right?" 22 | }, 23 | typescript: { 24 | language: "typescript", 25 | message: "Oooh, look at me, my javascript is soo fancy!!" 26 | }, 27 | python: { 28 | language: "python", 29 | message: 30 | 'Tell me the truth, you have as many friends in real life as you have ";" in your code, right?' 31 | }, 32 | python3: { 33 | language: "python3", 34 | message: "Want a scale to measure that indentation?" 35 | }, 36 | csharp: { 37 | language: "csharp", 38 | message: 39 | "You know, if you point a million monkeys at a million keyboards, one of them will eventually write a Java program. The rest of them will write C# programs." 40 | }, 41 | ruby: { 42 | language: "ruby", 43 | message: "Go and learn a real language." 44 | }, 45 | swift: { 46 | language: "swift", 47 | message: "Wasn't this supposed to be optional?" 48 | }, 49 | go: { 50 | language: "go", 51 | message: "Go, just keep going, don't stop, don't look back, just go." 52 | }, 53 | erlang: { 54 | language: "erlang", 55 | message: "Oh! Hello Grandpa!" 56 | }, 57 | elixir: { 58 | language: "elixir", 59 | message: "You sound like a language used in Lord of the Rings." 60 | }, 61 | dart: { 62 | language: "dart", 63 | message: "Just learn a real language please." 64 | }, 65 | kotlin: { 66 | language: "kotlin", 67 | message: "Seriously? Kotlin for leetcode?" 68 | }, 69 | php: { 70 | language: "php", 71 | message: "Just stick to web development." 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contents/CongratulationsModal.tsx: -------------------------------------------------------------------------------- 1 | import styleText from "data-text:./modal.css" 2 | import type { PlasmoCSConfig } from "plasmo" 3 | import React, { useEffect, useState } from "react" 4 | 5 | import { messages } from "../constants" 6 | 7 | export const config: PlasmoCSConfig = { 8 | matches: ["https://leetcode.com/*"] 9 | } 10 | // Have to do this in order to get the css to apply (see the plasmo docs for more info) 11 | export const getStyle = () => { 12 | const style = document.createElement("style") 13 | style.textContent = styleText 14 | return style 15 | } 16 | 17 | const CongratulationsModal = () => { 18 | const [showModal, setShowModal] = useState(false) 19 | const [language, setLanguage] = useState("") 20 | useEffect(() => { 21 | const handleClick = (event: any) => { 22 | let currentTarget = event.target 23 | while (currentTarget) { 24 | if ( 25 | currentTarget.matches( 26 | 'button[data-e2e-locator="console-submit-button"]' 27 | ) 28 | ) { 29 | chrome.runtime.sendMessage({ action: "userClickedSubmit" }) 30 | } 31 | // We hit a child element, so we go up the DOM until we're at the button 32 | currentTarget = currentTarget.parentElement 33 | } 34 | } 35 | const handleMessage = (message: any, sender: any, sendResponse: any) => { 36 | if (message.action === "userSolvedProblem") { 37 | setShowModal(true) 38 | setLanguage(message.language) 39 | } 40 | } 41 | 42 | // Listen for user interaction, e.g., when they click "Submit". 43 | document.addEventListener("click", handleClick) 44 | 45 | // Listen for messages from the background script or other parts of the extension. 46 | chrome.runtime.onMessage.addListener(handleMessage) 47 | 48 | // Cleanup on component unmount 49 | return () => { 50 | document.removeEventListener("click", handleClick) 51 | chrome.runtime.onMessage.removeListener(handleMessage) 52 | } 53 | }, []) 54 | 55 | return ( 56 | showModal && ( 57 |
58 |
59 |

Congratulations! You've solved the problem!

60 |

{messages[language].message}

61 | 66 |
67 |
68 | ) 69 | ) 70 | } 71 | 72 | export default CongratulationsModal 73 | -------------------------------------------------------------------------------- /contents/FailedModal.tsx: -------------------------------------------------------------------------------- 1 | import styleText from "data-text:./modal.css" 2 | import type { PlasmoCSConfig } from "plasmo" 3 | import React, { useEffect, useState } from "react" 4 | 5 | import FailImage from "~components/FailImage" 6 | 7 | export const config: PlasmoCSConfig = { 8 | matches: ["https://leetcode.com/*"] 9 | } 10 | // Have to do this in order to get the css to apply (see the plasmo docs for more info) 11 | export const getStyle = () => { 12 | const style = document.createElement("style") 13 | style.textContent = styleText 14 | return style 15 | } 16 | 17 | const FailedModal = () => { 18 | const [showModal, setShowModal] = useState(false) 19 | 20 | useEffect(() => { 21 | const handleClick = (event: any) => { 22 | let currentTarget = event.target 23 | while (currentTarget) { 24 | if ( 25 | currentTarget.matches( 26 | 'button[data-e2e-locator="console-submit-button"]' 27 | ) 28 | ) { 29 | chrome.runtime.sendMessage({ action: "userClickedSubmit" }) 30 | } 31 | // We hit a child element, so we go up the DOM until we're at the button 32 | currentTarget = currentTarget.parentElement 33 | } 34 | } 35 | const handleMessage = (message: any, sender: any, sendResponse: any) => { 36 | if (message.action === "userFailedProblem") { 37 | setShowModal(true) 38 | } 39 | } 40 | 41 | // Listen for user interaction, e.g., when they click "Submit". 42 | document.addEventListener("click", handleClick) 43 | 44 | // Listen for messages from the background script or other parts of the extension. 45 | chrome.runtime.onMessage.addListener(handleMessage) 46 | 47 | // Cleanup on component unmount 48 | return () => { 49 | document.removeEventListener("click", handleClick) 50 | chrome.runtime.onMessage.removeListener(handleMessage) 51 | } 52 | }, []) 53 | 54 | return ( 55 | showModal && ( 56 |
57 |
58 |

Congratulations! You've made a mistake!

59 | 60 |
61 | 62 |
63 |

The Hyper Torture streak has been reset to zero.

64 | 69 |
70 |
71 | ) 72 | ) 73 | } 74 | 75 | export default FailedModal 76 | -------------------------------------------------------------------------------- /contents/modal.css: -------------------------------------------------------------------------------- 1 | .modal-background { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | background: rgba(0, 0, 0, 0.5); 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | z-index: 9999; 12 | } 13 | 14 | .modal-content { 15 | background: #fff; 16 | padding: 20px; 17 | border-radius: 8px; 18 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); 19 | color: black; 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | 26 | .modal-content h1 { 27 | font-size: 1.5rem; 28 | } 29 | 30 | .close-modal-button { 31 | width: 10rem; 32 | padding: 0.75rem; 33 | font-size: 1rem; 34 | border-radius: 10px; 35 | color: white; 36 | background-color: #4f46e5; 37 | font-weight: 600; 38 | } 39 | 40 | .close-modal-button:hover { 41 | cursor: pointer; 42 | background-color: #3c34b5; 43 | } 44 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | verbose: true, 4 | preset: "jest-puppeteer", 5 | transform: { 6 | "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "babel-jest" 7 | } 8 | } 9 | 10 | module.exports = config 11 | -------------------------------------------------------------------------------- /leetcode-problems/blind75Problems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "category": "Arrays & Hashing", 4 | "href": "https://leetcode.com/problems/contains-duplicate/", 5 | "text": "Contains Duplicate", 6 | "difficulty": "Easy", 7 | "isPremium": false 8 | }, 9 | { 10 | "category": "Arrays & Hashing", 11 | "href": "https://leetcode.com/problems/valid-anagram/", 12 | "text": "Valid Anagram", 13 | "difficulty": "Easy", 14 | "isPremium": false 15 | }, 16 | { 17 | "category": "Arrays & Hashing", 18 | "href": "https://leetcode.com/problems/two-sum/", 19 | "text": "Two Sum", 20 | "difficulty": "Easy", 21 | "isPremium": false 22 | }, 23 | { 24 | "category": "Arrays & Hashing", 25 | "href": "https://leetcode.com/problems/group-anagrams/", 26 | "text": "Group Anagrams", 27 | "difficulty": "Medium", 28 | "isPremium": false 29 | }, 30 | { 31 | "category": "Arrays & Hashing", 32 | "href": "https://leetcode.com/problems/top-k-frequent-elements/", 33 | "text": "Top K Frequent Elements", 34 | "difficulty": "Medium", 35 | "isPremium": false 36 | }, 37 | { 38 | "category": "Arrays & Hashing", 39 | "href": "https://leetcode.com/problems/product-of-array-except-self/", 40 | "text": "Product of Array Except Self", 41 | "difficulty": "Medium", 42 | "isPremium": false 43 | }, 44 | { 45 | "category": "Arrays & Hashing", 46 | "href": "https://leetcode.com/problems/encode-and-decode-strings/", 47 | "text": "Encode and Decode Strings", 48 | "difficulty": "Medium", 49 | "isPremium": true 50 | }, 51 | { 52 | "category": "Arrays & Hashing", 53 | "href": "https://leetcode.com/problems/longest-consecutive-sequence/", 54 | "text": "Longest Consecutive Sequence", 55 | "difficulty": "Medium", 56 | "isPremium": false 57 | }, 58 | { 59 | "category": "Two Pointers", 60 | "href": "https://leetcode.com/problems/valid-palindrome/", 61 | "text": "Valid Palindrome", 62 | "difficulty": "Easy", 63 | "isPremium": false 64 | }, 65 | { 66 | "category": "Two Pointers", 67 | "href": "https://leetcode.com/problems/3sum/", 68 | "text": "3Sum", 69 | "difficulty": "Medium", 70 | "isPremium": false 71 | }, 72 | { 73 | "category": "Two Pointers", 74 | "href": "https://leetcode.com/problems/container-with-most-water/", 75 | "text": "Container With Most Water", 76 | "difficulty": "Medium", 77 | "isPremium": false 78 | }, 79 | { 80 | "category": "Sliding Window", 81 | "href": "https://leetcode.com/problems/best-time-to-buy-and-sell-stock/", 82 | "text": "Best Time to Buy And Sell Stock", 83 | "difficulty": "Easy", 84 | "isPremium": false 85 | }, 86 | { 87 | "category": "Sliding Window", 88 | "href": "https://leetcode.com/problems/longest-substring-without-repeating-characters/", 89 | "text": "Longest Substring Without Repeating Characters", 90 | "difficulty": "Medium", 91 | "isPremium": false 92 | }, 93 | { 94 | "category": "Sliding Window", 95 | "href": "https://leetcode.com/problems/longest-repeating-character-replacement/", 96 | "text": "Longest Repeating Character Replacement", 97 | "difficulty": "Medium", 98 | "isPremium": false 99 | }, 100 | { 101 | "category": "Sliding Window", 102 | "href": "https://leetcode.com/problems/minimum-window-substring/", 103 | "text": "Minimum Window Substring", 104 | "difficulty": "Hard", 105 | "isPremium": false 106 | }, 107 | { 108 | "category": "Stack", 109 | "href": "https://leetcode.com/problems/valid-parentheses/", 110 | "text": "Valid Parentheses", 111 | "difficulty": "Easy", 112 | "isPremium": false 113 | }, 114 | { 115 | "category": "Binary Search", 116 | "href": "https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/", 117 | "text": "Find Minimum In Rotated Sorted Array", 118 | "difficulty": "Medium", 119 | "isPremium": false 120 | }, 121 | { 122 | "category": "Binary Search", 123 | "href": "https://leetcode.com/problems/search-in-rotated-sorted-array/", 124 | "text": "Search In Rotated Sorted Array", 125 | "difficulty": "Medium", 126 | "isPremium": false 127 | }, 128 | { 129 | "category": "Linked List", 130 | "href": "https://leetcode.com/problems/reverse-linked-list/", 131 | "text": "Reverse Linked List", 132 | "difficulty": "Easy", 133 | "isPremium": false 134 | }, 135 | { 136 | "category": "Linked List", 137 | "href": "https://leetcode.com/problems/merge-two-sorted-lists/", 138 | "text": "Merge Two Sorted Lists", 139 | "difficulty": "Easy", 140 | "isPremium": false 141 | }, 142 | { 143 | "category": "Linked List", 144 | "href": "https://leetcode.com/problems/reorder-list/", 145 | "text": "Reorder List", 146 | "difficulty": "Medium", 147 | "isPremium": false 148 | }, 149 | { 150 | "category": "Linked List", 151 | "href": "https://leetcode.com/problems/remove-nth-node-from-end-of-list/", 152 | "text": "Remove Nth Node From End of List", 153 | "difficulty": "Medium", 154 | "isPremium": false 155 | }, 156 | { 157 | "category": "Linked List", 158 | "href": "https://leetcode.com/problems/linked-list-cycle/", 159 | "text": "Linked List Cycle", 160 | "difficulty": "Easy", 161 | "isPremium": false 162 | }, 163 | { 164 | "category": "Linked List", 165 | "href": "https://leetcode.com/problems/merge-k-sorted-lists/", 166 | "text": "Merge K Sorted Lists", 167 | "difficulty": "Hard", 168 | "isPremium": false 169 | }, 170 | { 171 | "category": "Trees", 172 | "href": "https://leetcode.com/problems/invert-binary-tree/", 173 | "text": "Invert Binary Tree", 174 | "difficulty": "Easy", 175 | "isPremium": false 176 | }, 177 | { 178 | "category": "Trees", 179 | "href": "https://leetcode.com/problems/maximum-depth-of-binary-tree/", 180 | "text": "Maximum Depth of Binary Tree", 181 | "difficulty": "Easy", 182 | "isPremium": false 183 | }, 184 | { 185 | "category": "Trees", 186 | "href": "https://leetcode.com/problems/same-tree/", 187 | "text": "Same Tree", 188 | "difficulty": "Easy", 189 | "isPremium": false 190 | }, 191 | { 192 | "category": "Trees", 193 | "href": "https://leetcode.com/problems/subtree-of-another-tree/", 194 | "text": "Subtree of Another Tree", 195 | "difficulty": "Easy", 196 | "isPremium": false 197 | }, 198 | { 199 | "category": "Trees", 200 | "href": "https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/", 201 | "text": "Lowest Common Ancestor of a Binary Search Tree", 202 | "difficulty": "Medium", 203 | "isPremium": false 204 | }, 205 | { 206 | "category": "Trees", 207 | "href": "https://leetcode.com/problems/binary-tree-level-order-traversal/", 208 | "text": "Binary Tree Level Order Traversal", 209 | "difficulty": "Medium", 210 | "isPremium": false 211 | }, 212 | { 213 | "category": "Trees", 214 | "href": "https://leetcode.com/problems/validate-binary-search-tree/", 215 | "text": "Validate Binary Search Tree", 216 | "difficulty": "Medium", 217 | "isPremium": false 218 | }, 219 | { 220 | "category": "Trees", 221 | "href": "https://leetcode.com/problems/kth-smallest-element-in-a-bst/", 222 | "text": "Kth Smallest Element In a Bst", 223 | "difficulty": "Medium", 224 | "isPremium": false 225 | }, 226 | { 227 | "category": "Trees", 228 | "href": "https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/", 229 | "text": "Construct Binary Tree From Preorder And Inorder Traversal", 230 | "difficulty": "Medium", 231 | "isPremium": false 232 | }, 233 | { 234 | "category": "Trees", 235 | "href": "https://leetcode.com/problems/binary-tree-maximum-path-sum/", 236 | "text": "Binary Tree Maximum Path Sum", 237 | "difficulty": "Hard", 238 | "isPremium": false 239 | }, 240 | { 241 | "category": "Trees", 242 | "href": "https://leetcode.com/problems/serialize-and-deserialize-binary-tree/", 243 | "text": "Serialize And Deserialize Binary Tree", 244 | "difficulty": "Hard", 245 | "isPremium": false 246 | }, 247 | { 248 | "category": "Tries", 249 | "href": "https://leetcode.com/problems/implement-trie-prefix-tree/", 250 | "text": "Implement Trie Prefix Tree", 251 | "difficulty": "Medium", 252 | "isPremium": false 253 | }, 254 | { 255 | "category": "Tries", 256 | "href": "https://leetcode.com/problems/design-add-and-search-words-data-structure/", 257 | "text": "Design Add And Search Words Data Structure", 258 | "difficulty": "Medium", 259 | "isPremium": false 260 | }, 261 | { 262 | "category": "Tries", 263 | "href": "https://leetcode.com/problems/word-search-ii/", 264 | "text": "Word Search II", 265 | "difficulty": "Hard", 266 | "isPremium": false 267 | }, 268 | { 269 | "category": "Heap / Priority Queue", 270 | "href": "https://leetcode.com/problems/find-median-from-data-stream/", 271 | "text": "Find Median From Data Stream", 272 | "difficulty": "Hard", 273 | "isPremium": false 274 | }, 275 | { 276 | "category": "Backtracking", 277 | "href": "https://leetcode.com/problems/combination-sum/", 278 | "text": "Combination Sum", 279 | "difficulty": "Medium", 280 | "isPremium": false 281 | }, 282 | { 283 | "category": "Backtracking", 284 | "href": "https://leetcode.com/problems/word-search/", 285 | "text": "Word Search", 286 | "difficulty": "Medium", 287 | "isPremium": false 288 | }, 289 | { 290 | "category": "Graphs", 291 | "href": "https://leetcode.com/problems/number-of-islands/", 292 | "text": "Number of Islands", 293 | "difficulty": "Medium", 294 | "isPremium": false 295 | }, 296 | { 297 | "category": "Graphs", 298 | "href": "https://leetcode.com/problems/clone-graph/", 299 | "text": "Clone Graph", 300 | "difficulty": "Medium", 301 | "isPremium": false 302 | }, 303 | { 304 | "category": "Graphs", 305 | "href": "https://leetcode.com/problems/pacific-atlantic-water-flow/", 306 | "text": "Pacific Atlantic Water Flow", 307 | "difficulty": "Medium", 308 | "isPremium": false 309 | }, 310 | { 311 | "category": "Graphs", 312 | "href": "https://leetcode.com/problems/course-schedule/", 313 | "text": "Course Schedule", 314 | "difficulty": "Medium", 315 | "isPremium": false 316 | }, 317 | { 318 | "category": "Graphs", 319 | "href": "https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/", 320 | "text": "Number of Connected Components In An Undirected Graph", 321 | "difficulty": "Medium", 322 | "isPremium": true 323 | }, 324 | { 325 | "category": "Graphs", 326 | "href": "https://leetcode.com/problems/graph-valid-tree/", 327 | "text": "Graph Valid Tree", 328 | "difficulty": "Medium", 329 | "isPremium": true 330 | }, 331 | { 332 | "category": "Advanced Graphs", 333 | "href": "https://leetcode.com/problems/alien-dictionary/", 334 | "text": "Alien Dictionary", 335 | "difficulty": "Hard", 336 | "isPremium": true 337 | }, 338 | { 339 | "category": "1-D Dynamic Programming", 340 | "href": "https://leetcode.com/problems/climbing-stairs/", 341 | "text": "Climbing Stairs", 342 | "difficulty": "Easy", 343 | "isPremium": false 344 | }, 345 | { 346 | "category": "1-D Dynamic Programming", 347 | "href": "https://leetcode.com/problems/house-robber/", 348 | "text": "House Robber", 349 | "difficulty": "Medium", 350 | "isPremium": false 351 | }, 352 | { 353 | "category": "1-D Dynamic Programming", 354 | "href": "https://leetcode.com/problems/house-robber-ii/", 355 | "text": "House Robber II", 356 | "difficulty": "Medium", 357 | "isPremium": false 358 | }, 359 | { 360 | "category": "1-D Dynamic Programming", 361 | "href": "https://leetcode.com/problems/longest-palindromic-substring/", 362 | "text": "Longest Palindromic Substring", 363 | "difficulty": "Medium", 364 | "isPremium": false 365 | }, 366 | { 367 | "category": "1-D Dynamic Programming", 368 | "href": "https://leetcode.com/problems/palindromic-substrings/", 369 | "text": "Palindromic Substrings", 370 | "difficulty": "Medium", 371 | "isPremium": false 372 | }, 373 | { 374 | "category": "1-D Dynamic Programming", 375 | "href": "https://leetcode.com/problems/decode-ways/", 376 | "text": "Decode Ways", 377 | "difficulty": "Medium", 378 | "isPremium": false 379 | }, 380 | { 381 | "category": "1-D Dynamic Programming", 382 | "href": "https://leetcode.com/problems/coin-change/", 383 | "text": "Coin Change", 384 | "difficulty": "Medium", 385 | "isPremium": false 386 | }, 387 | { 388 | "category": "1-D Dynamic Programming", 389 | "href": "https://leetcode.com/problems/maximum-product-subarray/", 390 | "text": "Maximum Product Subarray", 391 | "difficulty": "Medium", 392 | "isPremium": false 393 | }, 394 | { 395 | "category": "1-D Dynamic Programming", 396 | "href": "https://leetcode.com/problems/word-break/", 397 | "text": "Word Break", 398 | "difficulty": "Medium", 399 | "isPremium": false 400 | }, 401 | { 402 | "category": "1-D Dynamic Programming", 403 | "href": "https://leetcode.com/problems/longest-increasing-subsequence/", 404 | "text": "Longest Increasing Subsequence", 405 | "difficulty": "Medium", 406 | "isPremium": false 407 | }, 408 | { 409 | "category": "2-D Dynamic Programming", 410 | "href": "https://leetcode.com/problems/unique-paths/", 411 | "text": "Unique Paths", 412 | "difficulty": "Medium", 413 | "isPremium": false 414 | }, 415 | { 416 | "category": "2-D Dynamic Programming", 417 | "href": "https://leetcode.com/problems/longest-common-subsequence/", 418 | "text": "Longest Common Subsequence", 419 | "difficulty": "Medium", 420 | "isPremium": false 421 | }, 422 | { 423 | "category": "Greedy", 424 | "href": "https://leetcode.com/problems/maximum-subarray/", 425 | "text": "Maximum Subarray", 426 | "difficulty": "Medium", 427 | "isPremium": false 428 | }, 429 | { 430 | "category": "Greedy", 431 | "href": "https://leetcode.com/problems/jump-game/", 432 | "text": "Jump Game", 433 | "difficulty": "Medium", 434 | "isPremium": false 435 | }, 436 | { 437 | "category": "Intervals", 438 | "href": "https://leetcode.com/problems/insert-interval/", 439 | "text": "Insert Interval", 440 | "difficulty": "Medium", 441 | "isPremium": false 442 | }, 443 | { 444 | "category": "Intervals", 445 | "href": "https://leetcode.com/problems/merge-intervals/", 446 | "text": "Merge Intervals", 447 | "difficulty": "Medium", 448 | "isPremium": false 449 | }, 450 | { 451 | "category": "Intervals", 452 | "href": "https://leetcode.com/problems/non-overlapping-intervals/", 453 | "text": "Non Overlapping Intervals", 454 | "difficulty": "Medium", 455 | "isPremium": false 456 | }, 457 | { 458 | "category": "Intervals", 459 | "href": "https://leetcode.com/problems/meeting-rooms/", 460 | "text": "Meeting Rooms", 461 | "difficulty": "Easy", 462 | "isPremium": true 463 | }, 464 | { 465 | "category": "Intervals", 466 | "href": "https://leetcode.com/problems/meeting-rooms-ii/", 467 | "text": "Meeting Rooms II", 468 | "difficulty": "Medium", 469 | "isPremium": true 470 | }, 471 | { 472 | "category": "Math & Geometry", 473 | "href": "https://leetcode.com/problems/rotate-image/", 474 | "text": "Rotate Image", 475 | "difficulty": "Medium", 476 | "isPremium": false 477 | }, 478 | { 479 | "category": "Math & Geometry", 480 | "href": "https://leetcode.com/problems/spiral-matrix/", 481 | "text": "Spiral Matrix", 482 | "difficulty": "Medium", 483 | "isPremium": false 484 | }, 485 | { 486 | "category": "Math & Geometry", 487 | "href": "https://leetcode.com/problems/set-matrix-zeroes/", 488 | "text": "Set Matrix Zeroes", 489 | "difficulty": "Medium", 490 | "isPremium": false 491 | }, 492 | { 493 | "category": "Bit Manipulation", 494 | "href": "https://leetcode.com/problems/number-of-1-bits/", 495 | "text": "Number of 1 Bits", 496 | "difficulty": "Easy", 497 | "isPremium": false 498 | }, 499 | { 500 | "category": "Bit Manipulation", 501 | "href": "https://leetcode.com/problems/counting-bits/", 502 | "text": "Counting Bits", 503 | "difficulty": "Easy", 504 | "isPremium": false 505 | }, 506 | { 507 | "category": "Bit Manipulation", 508 | "href": "https://leetcode.com/problems/reverse-bits/", 509 | "text": "Reverse Bits", 510 | "difficulty": "Easy", 511 | "isPremium": false 512 | }, 513 | { 514 | "category": "Bit Manipulation", 515 | "href": "https://leetcode.com/problems/missing-number/", 516 | "text": "Missing Number", 517 | "difficulty": "Easy", 518 | "isPremium": false 519 | }, 520 | { 521 | "category": "Bit Manipulation", 522 | "href": "https://leetcode.com/problems/sum-of-two-integers/", 523 | "text": "Sum of Two Integers", 524 | "difficulty": "Medium", 525 | "isPremium": false 526 | } 527 | ] 528 | -------------------------------------------------------------------------------- /leetcode-problems/metaTop100.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "category": null, 4 | "href": "https://leetcode.com/problems/binary-tree-vertical-order-traversal", 5 | "text": "Binary Tree Vertical Order Traversal", 6 | "difficulty": "Medium", 7 | "isPremium": false 8 | }, 9 | { 10 | "category": null, 11 | "href": "https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses", 12 | "text": "Minimum Remove to Make Valid Parentheses", 13 | "difficulty": "Medium", 14 | "isPremium": false 15 | }, 16 | { 17 | "category": null, 18 | "href": "https://leetcode.com/problems/valid-word-abbreviation", 19 | "text": "Valid Word Abbreviation", 20 | "difficulty": "Easy", 21 | "isPremium": false 22 | }, 23 | { 24 | "category": null, 25 | "href": "https://leetcode.com/problems/nested-list-weight-sum", 26 | "text": "Nested List Weight Sum", 27 | "difficulty": "Medium", 28 | "isPremium": false 29 | }, 30 | { 31 | "category": null, 32 | "href": "https://leetcode.com/problems/dot-product-of-two-sparse-vectors", 33 | "text": "Dot Product of Two Sparse Vectors", 34 | "difficulty": "Medium", 35 | "isPremium": false 36 | }, 37 | { 38 | "category": null, 39 | "href": "https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-iii", 40 | "text": "Lowest Common Ancestor of a Binary Tree III", 41 | "difficulty": "Medium", 42 | "isPremium": false 43 | }, 44 | { 45 | "category": null, 46 | "href": "https://leetcode.com/problems/valid-palindrome-ii", 47 | "text": "Valid Palindrome II", 48 | "difficulty": "Easy", 49 | "isPremium": false 50 | }, 51 | { 52 | "category": null, 53 | "href": "https://leetcode.com/problems/buildings-with-an-ocean-view", 54 | "text": "Buildings With an Ocean View", 55 | "difficulty": "Medium", 56 | "isPremium": false 57 | }, 58 | { 59 | "category": null, 60 | "href": "https://leetcode.com/problems/basic-calculator-ii", 61 | "text": "Basic Calculator II", 62 | "difficulty": "Medium", 63 | "isPremium": false 64 | }, 65 | { 66 | "category": null, 67 | "href": "https://leetcode.com/problems/random-pick-with-weight", 68 | "text": "Random Pick with Weight", 69 | "difficulty": "Medium", 70 | "isPremium": false 71 | }, 72 | { 73 | "category": null, 74 | "href": "https://leetcode.com/problems/kth-largest-element-in-an-array", 75 | "text": "Kth Largest Element in an Array", 76 | "difficulty": "Medium", 77 | "isPremium": false 78 | }, 79 | { 80 | "category": null, 81 | "href": "https://leetcode.com/problems/range-sum-of-bst", 82 | "text": "Range Sum of BST", 83 | "difficulty": "Easy", 84 | "isPremium": false 85 | }, 86 | { 87 | "category": null, 88 | "href": "https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list", 89 | "text": "Convert Binary Search Tree to Sorted Doubly Linked List", 90 | "difficulty": "Medium", 91 | "isPremium": false 92 | }, 93 | { 94 | "category": null, 95 | "href": "https://leetcode.com/problems/moving-average-from-data-stream", 96 | "text": "Moving Average from Data Stream", 97 | "difficulty": "Easy", 98 | "isPremium": false 99 | }, 100 | { 101 | "category": null, 102 | "href": "https://leetcode.com/problems/simplify-path", 103 | "text": "Simplify Path", 104 | "difficulty": "Medium", 105 | "isPremium": false 106 | }, 107 | { 108 | "category": null, 109 | "href": "https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree", 110 | "text": "Lowest Common Ancestor of a Binary Tree", 111 | "difficulty": "Medium", 112 | "isPremium": false 113 | }, 114 | { 115 | "category": null, 116 | "href": "https://leetcode.com/problems/minimum-add-to-make-parentheses-valid", 117 | "text": "Minimum Add to Make Parentheses Valid", 118 | "difficulty": "Medium", 119 | "isPremium": false 120 | }, 121 | { 122 | "category": null, 123 | "href": "https://leetcode.com/problems/powx-n", 124 | "text": "Pow(x, n)", 125 | "difficulty": "Medium", 126 | "isPremium": false 127 | }, 128 | { 129 | "category": null, 130 | "href": "https://leetcode.com/problems/insert-into-a-sorted-circular-linked-list", 131 | "text": "Insert into a Sorted Circular Linked List", 132 | "difficulty": "Medium", 133 | "isPremium": false 134 | }, 135 | { 136 | "category": null, 137 | "href": "https://leetcode.com/problems/k-closest-points-to-origin", 138 | "text": "K Closest Points to Origin", 139 | "difficulty": "Medium", 140 | "isPremium": false 141 | }, 142 | { 143 | "category": null, 144 | "href": "https://leetcode.com/problems/sum-root-to-leaf-numbers", 145 | "text": "Sum Root to Leaf Numbers", 146 | "difficulty": "Medium", 147 | "isPremium": false 148 | }, 149 | { 150 | "category": null, 151 | "href": "https://leetcode.com/problems/find-peak-element", 152 | "text": "Find Peak Element", 153 | "difficulty": "Medium", 154 | "isPremium": false 155 | }, 156 | { 157 | "category": null, 158 | "href": "https://leetcode.com/problems/stickers-to-spell-word", 159 | "text": "Stickers to Spell Word", 160 | "difficulty": "Hard", 161 | "isPremium": false 162 | }, 163 | { 164 | "category": null, 165 | "href": "https://leetcode.com/problems/making-a-large-island", 166 | "text": "Making A Large Island", 167 | "difficulty": "Hard", 168 | "isPremium": false 169 | }, 170 | { 171 | "category": null, 172 | "href": "https://leetcode.com/problems/interval-list-intersections", 173 | "text": "Interval List Intersections", 174 | "difficulty": "Medium", 175 | "isPremium": false 176 | }, 177 | { 178 | "category": null, 179 | "href": "https://leetcode.com/problems/valid-number", 180 | "text": "Valid Number", 181 | "difficulty": "Hard", 182 | "isPremium": false 183 | }, 184 | { 185 | "category": null, 186 | "href": "https://leetcode.com/problems/binary-tree-right-side-view", 187 | "text": "Binary Tree Right Side View", 188 | "difficulty": "Medium", 189 | "isPremium": false 190 | }, 191 | { 192 | "category": null, 193 | "href": "https://leetcode.com/problems/shortest-path-in-binary-matrix", 194 | "text": "Shortest Path in Binary Matrix", 195 | "difficulty": "Medium", 196 | "isPremium": false 197 | }, 198 | { 199 | "category": null, 200 | "href": "https://leetcode.com/problems/diameter-of-binary-tree", 201 | "text": "Diameter of Binary Tree", 202 | "difficulty": "Easy", 203 | "isPremium": false 204 | }, 205 | { 206 | "category": null, 207 | "href": "https://leetcode.com/problems/merge-intervals", 208 | "text": "Merge Intervals", 209 | "difficulty": "Medium", 210 | "isPremium": false 211 | }, 212 | { 213 | "category": null, 214 | "href": "https://leetcode.com/problems/lru-cache", 215 | "text": "LRU Cache", 216 | "difficulty": "Medium", 217 | "isPremium": false 218 | }, 219 | { 220 | "category": null, 221 | "href": "https://leetcode.com/problems/random-pick-index", 222 | "text": "Random Pick Index", 223 | "difficulty": "Medium", 224 | "isPremium": false 225 | }, 226 | { 227 | "category": null, 228 | "href": "https://leetcode.com/problems/group-shifted-strings", 229 | "text": "Group Shifted Strings", 230 | "difficulty": "Medium", 231 | "isPremium": false 232 | }, 233 | { 234 | "category": null, 235 | "href": "https://leetcode.com/problems/maximum-swap", 236 | "text": "Maximum Swap", 237 | "difficulty": "Medium", 238 | "isPremium": false 239 | }, 240 | { 241 | "category": null, 242 | "href": "https://leetcode.com/problems/exclusive-time-of-functions", 243 | "text": "Exclusive Time of Functions", 244 | "difficulty": "Medium", 245 | "isPremium": false 246 | }, 247 | { 248 | "category": null, 249 | "href": "https://leetcode.com/problems/closest-binary-search-tree-value", 250 | "text": "Closest Binary Search Tree Value", 251 | "difficulty": "Easy", 252 | "isPremium": false 253 | }, 254 | { 255 | "category": null, 256 | "href": "https://leetcode.com/problems/custom-sort-string", 257 | "text": "Custom Sort String", 258 | "difficulty": "Medium", 259 | "isPremium": false 260 | }, 261 | { 262 | "category": null, 263 | "href": "https://leetcode.com/problems/subarray-sum-equals-k", 264 | "text": "Subarray Sum Equals K", 265 | "difficulty": "Medium", 266 | "isPremium": false 267 | }, 268 | { 269 | "category": null, 270 | "href": "https://leetcode.com/problems/copy-list-with-random-pointer", 271 | "text": "Copy List with Random Pointer", 272 | "difficulty": "Medium", 273 | "isPremium": false 274 | }, 275 | { 276 | "category": null, 277 | "href": "https://leetcode.com/problems/toeplitz-matrix", 278 | "text": "Toeplitz Matrix", 279 | "difficulty": "Easy", 280 | "isPremium": false 281 | }, 282 | { 283 | "category": null, 284 | "href": "https://leetcode.com/problems/vertical-order-traversal-of-a-binary-tree", 285 | "text": "Vertical Order Traversal of a Binary Tree", 286 | "difficulty": "Hard", 287 | "isPremium": false 288 | }, 289 | { 290 | "category": null, 291 | "href": "https://leetcode.com/problems/add-strings", 292 | "text": "Add Strings", 293 | "difficulty": "Easy", 294 | "isPremium": false 295 | }, 296 | { 297 | "category": null, 298 | "href": "https://leetcode.com/problems/next-permutation", 299 | "text": "Next Permutation", 300 | "difficulty": "Medium", 301 | "isPremium": false 302 | }, 303 | { 304 | "category": null, 305 | "href": "https://leetcode.com/problems/remove-invalid-parentheses", 306 | "text": "Remove Invalid Parentheses", 307 | "difficulty": "Hard", 308 | "isPremium": false 309 | }, 310 | { 311 | "category": null, 312 | "href": "https://leetcode.com/problems/merge-k-sorted-lists", 313 | "text": "Merge k Sorted Lists", 314 | "difficulty": "Hard", 315 | "isPremium": false 316 | }, 317 | { 318 | "category": null, 319 | "href": "https://leetcode.com/problems/merge-sorted-array", 320 | "text": "Merge Sorted Array", 321 | "difficulty": "Easy", 322 | "isPremium": false 323 | }, 324 | { 325 | "category": null, 326 | "href": "https://leetcode.com/problems/valid-palindrome-iii", 327 | "text": "Valid Palindrome III", 328 | "difficulty": "Hard", 329 | "isPremium": false 330 | }, 331 | { 332 | "category": null, 333 | "href": "https://leetcode.com/problems/top-k-frequent-elements", 334 | "text": "Top K Frequent Elements", 335 | "difficulty": "Medium", 336 | "isPremium": false 337 | }, 338 | { 339 | "category": null, 340 | "href": "https://leetcode.com/problems/all-nodes-distance-k-in-binary-tree", 341 | "text": "All Nodes Distance K in Binary Tree", 342 | "difficulty": "Medium", 343 | "isPremium": false 344 | }, 345 | { 346 | "category": null, 347 | "href": "https://leetcode.com/problems/valid-palindrome", 348 | "text": "Valid Palindrome", 349 | "difficulty": "Easy", 350 | "isPremium": false 351 | }, 352 | { 353 | "category": null, 354 | "href": "https://leetcode.com/problems/word-break-ii", 355 | "text": "Word Break II", 356 | "difficulty": "Hard", 357 | "isPremium": false 358 | }, 359 | { 360 | "category": null, 361 | "href": "https://leetcode.com/problems/3sum", 362 | "text": "3Sum", 363 | "difficulty": "Medium", 364 | "isPremium": false 365 | }, 366 | { 367 | "category": null, 368 | "href": "https://leetcode.com/problems/max-consecutive-ones-iii", 369 | "text": "Max Consecutive Ones III", 370 | "difficulty": "Medium", 371 | "isPremium": false 372 | }, 373 | { 374 | "category": null, 375 | "href": "https://leetcode.com/problems/product-of-two-run-length-encoded-arrays", 376 | "text": "Product of Two Run-Length Encoded Arrays", 377 | "difficulty": "Medium", 378 | "isPremium": false 379 | }, 380 | { 381 | "category": null, 382 | "href": "https://leetcode.com/problems/shortest-distance-from-all-buildings", 383 | "text": "Shortest Distance from All Buildings", 384 | "difficulty": "Hard", 385 | "isPremium": false 386 | }, 387 | { 388 | "category": null, 389 | "href": "https://leetcode.com/problems/accounts-merge", 390 | "text": "Accounts Merge", 391 | "difficulty": "Medium", 392 | "isPremium": false 393 | }, 394 | { 395 | "category": null, 396 | "href": "https://leetcode.com/problems/design-tic-tac-toe", 397 | "text": "Design Tic-Tac-Toe", 398 | "difficulty": "Medium", 399 | "isPremium": false 400 | }, 401 | { 402 | "category": null, 403 | "href": "https://leetcode.com/problems/move-zeroes", 404 | "text": "Move Zeroes", 405 | "difficulty": "Easy", 406 | "isPremium": false 407 | }, 408 | { 409 | "category": null, 410 | "href": "https://leetcode.com/problems/check-if-an-original-string-exists-given-two-encoded-strings", 411 | "text": "Check if an Original String Exists Given Two Encoded Strings", 412 | "difficulty": "Hard", 413 | "isPremium": false 414 | }, 415 | { 416 | "category": null, 417 | "href": "https://leetcode.com/problems/continuous-subarray-sum", 418 | "text": "Continuous Subarray Sum", 419 | "difficulty": "Medium", 420 | "isPremium": false 421 | }, 422 | { 423 | "category": null, 424 | "href": "https://leetcode.com/problems/missing-ranges", 425 | "text": "Missing Ranges", 426 | "difficulty": "Easy", 427 | "isPremium": false 428 | }, 429 | { 430 | "category": null, 431 | "href": "https://leetcode.com/problems/diagonal-traverse", 432 | "text": "Diagonal Traverse", 433 | "difficulty": "Medium", 434 | "isPremium": false 435 | }, 436 | { 437 | "category": null, 438 | "href": "https://leetcode.com/problems/clone-graph", 439 | "text": "Clone Graph", 440 | "difficulty": "Medium", 441 | "isPremium": false 442 | }, 443 | { 444 | "category": null, 445 | "href": "https://leetcode.com/problems/minimum-window-substring", 446 | "text": "Minimum Window Substring", 447 | "difficulty": "Hard", 448 | "isPremium": false 449 | }, 450 | { 451 | "category": null, 452 | "href": "https://leetcode.com/problems/number-of-islands", 453 | "text": "Number of Islands", 454 | "difficulty": "Medium", 455 | "isPremium": false 456 | }, 457 | { 458 | "category": null, 459 | "href": "https://leetcode.com/problems/best-meeting-point", 460 | "text": "Best Meeting Point", 461 | "difficulty": "Hard", 462 | "isPremium": false 463 | }, 464 | { 465 | "category": null, 466 | "href": "https://leetcode.com/problems/maximum-sum-of-3-non-overlapping-subarrays", 467 | "text": "Maximum Sum of 3 Non-Overlapping Subarrays", 468 | "difficulty": "Hard", 469 | "isPremium": false 470 | }, 471 | { 472 | "category": null, 473 | "href": "https://leetcode.com/problems/kth-missing-positive-number", 474 | "text": "Kth Missing Positive Number", 475 | "difficulty": "Easy", 476 | "isPremium": false 477 | }, 478 | { 479 | "category": null, 480 | "href": "https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string", 481 | "text": "Remove All Adjacent Duplicates In String", 482 | "difficulty": "Easy", 483 | "isPremium": false 484 | }, 485 | { 486 | "category": null, 487 | "href": "https://leetcode.com/problems/find-k-closest-elements", 488 | "text": "Find K Closest Elements", 489 | "difficulty": "Medium", 490 | "isPremium": false 491 | }, 492 | { 493 | "category": null, 494 | "href": "https://leetcode.com/problems/count-and-say", 495 | "text": "Count and Say", 496 | "difficulty": "Medium", 497 | "isPremium": false 498 | }, 499 | { 500 | "category": null, 501 | "href": "https://leetcode.com/problems/binary-search-tree-iterator", 502 | "text": "Binary Search Tree Iterator", 503 | "difficulty": "Medium", 504 | "isPremium": false 505 | }, 506 | { 507 | "category": null, 508 | "href": "https://leetcode.com/problems/minesweeper", 509 | "text": "Minesweeper", 510 | "difficulty": "Medium", 511 | "isPremium": false 512 | }, 513 | { 514 | "category": null, 515 | "href": "https://leetcode.com/problems/minimum-insertions-to-balance-a-parentheses-string", 516 | "text": "Minimum Insertions to Balance a Parentheses String", 517 | "difficulty": "Medium", 518 | "isPremium": false 519 | }, 520 | { 521 | "category": null, 522 | "href": "https://leetcode.com/problems/course-schedule", 523 | "text": "Course Schedule", 524 | "difficulty": "Medium", 525 | "isPremium": false 526 | }, 527 | { 528 | "category": null, 529 | "href": "https://leetcode.com/problems/verifying-an-alien-dictionary", 530 | "text": "Verifying an Alien Dictionary", 531 | "difficulty": "Easy", 532 | "isPremium": false 533 | }, 534 | { 535 | "category": null, 536 | "href": "https://leetcode.com/problems/letter-combinations-of-a-phone-number", 537 | "text": "Letter Combinations of a Phone Number", 538 | "difficulty": "Medium", 539 | "isPremium": false 540 | }, 541 | { 542 | "category": null, 543 | "href": "https://leetcode.com/problems/serialize-and-deserialize-n-ary-tree", 544 | "text": "Serialize and Deserialize N-ary Tree", 545 | "difficulty": "Hard", 546 | "isPremium": false 547 | }, 548 | { 549 | "category": null, 550 | "href": "https://leetcode.com/problems/expression-add-operators", 551 | "text": "Expression Add Operators", 552 | "difficulty": "Hard", 553 | "isPremium": false 554 | }, 555 | { 556 | "category": null, 557 | "href": "https://leetcode.com/problems/meeting-rooms-ii", 558 | "text": "Meeting Rooms II", 559 | "difficulty": "Medium", 560 | "isPremium": false 561 | }, 562 | { 563 | "category": null, 564 | "href": "https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix", 565 | "text": "Kth Smallest Element in a Sorted Matrix", 566 | "difficulty": "Medium", 567 | "isPremium": false 568 | }, 569 | { 570 | "category": null, 571 | "href": "https://leetcode.com/problems/sliding-window-median", 572 | "text": "Sliding Window Median", 573 | "difficulty": "Hard", 574 | "isPremium": false 575 | }, 576 | { 577 | "category": null, 578 | "href": "https://leetcode.com/problems/diagonal-traverse-ii", 579 | "text": "Diagonal Traverse II", 580 | "difficulty": "Medium", 581 | "isPremium": false 582 | }, 583 | { 584 | "category": null, 585 | "href": "https://leetcode.com/problems/range-sum-query-2d-immutable", 586 | "text": "Range Sum Query 2D - Immutable", 587 | "difficulty": "Medium", 588 | "isPremium": false 589 | }, 590 | { 591 | "category": null, 592 | "href": "https://leetcode.com/problems/friends-of-appropriate-ages", 593 | "text": "Friends Of Appropriate Ages", 594 | "difficulty": "Medium", 595 | "isPremium": false 596 | }, 597 | { 598 | "category": null, 599 | "href": "https://leetcode.com/problems/robot-room-cleaner", 600 | "text": "Robot Room Cleaner", 601 | "difficulty": "Hard", 602 | "isPremium": false 603 | }, 604 | { 605 | "category": null, 606 | "href": "https://leetcode.com/problems/max-area-of-island", 607 | "text": "Max Area of Island", 608 | "difficulty": "Medium", 609 | "isPremium": false 610 | }, 611 | { 612 | "category": null, 613 | "href": "https://leetcode.com/problems/task-scheduler-ii", 614 | "text": "Task Scheduler II", 615 | "difficulty": "Medium", 616 | "isPremium": false 617 | }, 618 | { 619 | "category": null, 620 | "href": "https://leetcode.com/problems/palindrome-permutation", 621 | "text": "Palindrome Permutation", 622 | "difficulty": "Easy", 623 | "isPremium": false 624 | }, 625 | { 626 | "category": null, 627 | "href": "https://leetcode.com/problems/rank-transform-of-an-array", 628 | "text": "Rank Transform of an Array", 629 | "difficulty": "Easy", 630 | "isPremium": false 631 | }, 632 | { 633 | "category": null, 634 | "href": "https://leetcode.com/problems/word-break", 635 | "text": "Word Break", 636 | "difficulty": "Medium", 637 | "isPremium": false 638 | }, 639 | { 640 | "category": null, 641 | "href": "https://leetcode.com/problems/longest-increasing-path-in-a-matrix", 642 | "text": "Longest Increasing Path in a Matrix", 643 | "difficulty": "Hard", 644 | "isPremium": false 645 | }, 646 | { 647 | "category": null, 648 | "href": "https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-ii", 649 | "text": "Lowest Common Ancestor of a Binary Tree II", 650 | "difficulty": "Medium", 651 | "isPremium": false 652 | }, 653 | { 654 | "category": null, 655 | "href": "https://leetcode.com/problems/minimum-window-subsequence", 656 | "text": "Minimum Window Subsequence", 657 | "difficulty": "Hard", 658 | "isPremium": false 659 | }, 660 | { 661 | "category": null, 662 | "href": "https://leetcode.com/problems/insert-delete-getrandom-o1", 663 | "text": "Insert Delete GetRandom O(1)", 664 | "difficulty": "Medium", 665 | "isPremium": false 666 | }, 667 | { 668 | "category": null, 669 | "href": "https://leetcode.com/problems/find-largest-value-in-each-tree-row", 670 | "text": "Find Largest Value in Each Tree Row", 671 | "difficulty": "Medium", 672 | "isPremium": false 673 | }, 674 | { 675 | "category": null, 676 | "href": "https://leetcode.com/problems/capacity-to-ship-packages-within-d-days", 677 | "text": "Capacity To Ship Packages Within D Days", 678 | "difficulty": "Medium", 679 | "isPremium": false 680 | }, 681 | { 682 | "category": null, 683 | "href": "https://leetcode.com/problems/string-to-integer-atoi", 684 | "text": "String to Integer (atoi)", 685 | "difficulty": "Medium", 686 | "isPremium": false 687 | }, 688 | { 689 | "category": null, 690 | "href": "https://leetcode.com/problems/maximum-number-of-intersections-on-the-chart", 691 | "text": "Maximum Number of Intersections on the Chart", 692 | "difficulty": "Hard", 693 | "isPremium": false 694 | }, 695 | { 696 | "category": null, 697 | "href": "https://leetcode.com/problems/construct-binary-tree-from-string", 698 | "text": "Construct Binary Tree from String", 699 | "difficulty": "Medium", 700 | "isPremium": false 701 | } 702 | ] -------------------------------------------------------------------------------- /leetcode-problems/neetCode150Problems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "category": "Arrays & Hashing", 4 | "href": "https://leetcode.com/problems/contains-duplicate/", 5 | "text": "Contains Duplicate", 6 | "difficulty": "Easy", 7 | "isPremium": false 8 | }, 9 | { 10 | "category": "Arrays & Hashing", 11 | "href": "https://leetcode.com/problems/valid-anagram/", 12 | "text": "Valid Anagram", 13 | "difficulty": "Easy", 14 | "isPremium": false 15 | }, 16 | { 17 | "category": "Arrays & Hashing", 18 | "href": "https://leetcode.com/problems/two-sum/", 19 | "text": "Two Sum", 20 | "difficulty": "Easy", 21 | "isPremium": false 22 | }, 23 | { 24 | "category": "Arrays & Hashing", 25 | "href": "https://leetcode.com/problems/group-anagrams/", 26 | "text": "Group Anagrams", 27 | "difficulty": "Medium", 28 | "isPremium": false 29 | }, 30 | { 31 | "category": "Arrays & Hashing", 32 | "href": "https://leetcode.com/problems/top-k-frequent-elements/", 33 | "text": "Top K Frequent Elements", 34 | "difficulty": "Medium", 35 | "isPremium": false 36 | }, 37 | { 38 | "category": "Arrays & Hashing", 39 | "href": "https://leetcode.com/problems/product-of-array-except-self/", 40 | "text": "Product of Array Except Self", 41 | "difficulty": "Medium", 42 | "isPremium": false 43 | }, 44 | { 45 | "category": "Arrays & Hashing", 46 | "href": "https://leetcode.com/problems/valid-sudoku/", 47 | "text": "Valid Sudoku", 48 | "difficulty": "Medium", 49 | "isPremium": false 50 | }, 51 | { 52 | "category": "Arrays & Hashing", 53 | "href": "https://leetcode.com/problems/encode-and-decode-strings/", 54 | "text": "Encode and Decode Strings", 55 | "difficulty": "Medium", 56 | "isPremium": true 57 | }, 58 | { 59 | "category": "Arrays & Hashing", 60 | "href": "https://leetcode.com/problems/longest-consecutive-sequence/", 61 | "text": "Longest Consecutive Sequence", 62 | "difficulty": "Medium", 63 | "isPremium": false 64 | }, 65 | { 66 | "category": "Two Pointers", 67 | "href": "https://leetcode.com/problems/valid-palindrome/", 68 | "text": "Valid Palindrome", 69 | "difficulty": "Easy", 70 | "isPremium": false 71 | }, 72 | { 73 | "category": "Two Pointers", 74 | "href": "https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/", 75 | "text": "Two Sum II Input Array Is Sorted", 76 | "difficulty": "Medium", 77 | "isPremium": false 78 | }, 79 | { 80 | "category": "Two Pointers", 81 | "href": "https://leetcode.com/problems/3sum/", 82 | "text": "3Sum", 83 | "difficulty": "Medium", 84 | "isPremium": false 85 | }, 86 | { 87 | "category": "Two Pointers", 88 | "href": "https://leetcode.com/problems/container-with-most-water/", 89 | "text": "Container With Most Water", 90 | "difficulty": "Medium", 91 | "isPremium": false 92 | }, 93 | { 94 | "category": "Two Pointers", 95 | "href": "https://leetcode.com/problems/trapping-rain-water/", 96 | "text": "Trapping Rain Water", 97 | "difficulty": "Hard", 98 | "isPremium": false 99 | }, 100 | { 101 | "category": "Sliding Window", 102 | "href": "https://leetcode.com/problems/best-time-to-buy-and-sell-stock/", 103 | "text": "Best Time to Buy And Sell Stock", 104 | "difficulty": "Easy", 105 | "isPremium": false 106 | }, 107 | { 108 | "category": "Sliding Window", 109 | "href": "https://leetcode.com/problems/longest-substring-without-repeating-characters/", 110 | "text": "Longest Substring Without Repeating Characters", 111 | "difficulty": "Medium", 112 | "isPremium": false 113 | }, 114 | { 115 | "category": "Sliding Window", 116 | "href": "https://leetcode.com/problems/longest-repeating-character-replacement/", 117 | "text": "Longest Repeating Character Replacement", 118 | "difficulty": "Medium", 119 | "isPremium": false 120 | }, 121 | { 122 | "category": "Sliding Window", 123 | "href": "https://leetcode.com/problems/permutation-in-string/", 124 | "text": "Permutation In String", 125 | "difficulty": "Medium", 126 | "isPremium": false 127 | }, 128 | { 129 | "category": "Sliding Window", 130 | "href": "https://leetcode.com/problems/minimum-window-substring/", 131 | "text": "Minimum Window Substring", 132 | "difficulty": "Hard", 133 | "isPremium": false 134 | }, 135 | { 136 | "category": "Sliding Window", 137 | "href": "https://leetcode.com/problems/sliding-window-maximum/", 138 | "text": "Sliding Window Maximum", 139 | "difficulty": "Hard", 140 | "isPremium": false 141 | }, 142 | { 143 | "category": "Stack", 144 | "href": "https://leetcode.com/problems/valid-parentheses/", 145 | "text": "Valid Parentheses", 146 | "difficulty": "Easy", 147 | "isPremium": false 148 | }, 149 | { 150 | "category": "Stack", 151 | "href": "https://leetcode.com/problems/min-stack/", 152 | "text": "Min Stack", 153 | "difficulty": "Medium", 154 | "isPremium": false 155 | }, 156 | { 157 | "category": "Stack", 158 | "href": "https://leetcode.com/problems/evaluate-reverse-polish-notation/", 159 | "text": "Evaluate Reverse Polish Notation", 160 | "difficulty": "Medium", 161 | "isPremium": false 162 | }, 163 | { 164 | "category": "Stack", 165 | "href": "https://leetcode.com/problems/generate-parentheses/", 166 | "text": "Generate Parentheses", 167 | "difficulty": "Medium", 168 | "isPremium": false 169 | }, 170 | { 171 | "category": "Stack", 172 | "href": "https://leetcode.com/problems/daily-temperatures/", 173 | "text": "Daily Temperatures", 174 | "difficulty": "Medium", 175 | "isPremium": false 176 | }, 177 | { 178 | "category": "Stack", 179 | "href": "https://leetcode.com/problems/car-fleet/", 180 | "text": "Car Fleet", 181 | "difficulty": "Medium", 182 | "isPremium": false 183 | }, 184 | { 185 | "category": "Stack", 186 | "href": "https://leetcode.com/problems/largest-rectangle-in-histogram/", 187 | "text": "Largest Rectangle In Histogram", 188 | "difficulty": "Hard", 189 | "isPremium": false 190 | }, 191 | { 192 | "category": "Binary Search", 193 | "href": "https://leetcode.com/problems/binary-search/", 194 | "text": "Binary Search", 195 | "difficulty": "Easy", 196 | "isPremium": false 197 | }, 198 | { 199 | "category": "Binary Search", 200 | "href": "https://leetcode.com/problems/search-a-2d-matrix/", 201 | "text": "Search a 2D Matrix", 202 | "difficulty": "Medium", 203 | "isPremium": false 204 | }, 205 | { 206 | "category": "Binary Search", 207 | "href": "https://leetcode.com/problems/koko-eating-bananas/", 208 | "text": "Koko Eating Bananas", 209 | "difficulty": "Medium", 210 | "isPremium": false 211 | }, 212 | { 213 | "category": "Binary Search", 214 | "href": "https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/", 215 | "text": "Find Minimum In Rotated Sorted Array", 216 | "difficulty": "Medium", 217 | "isPremium": false 218 | }, 219 | { 220 | "category": "Binary Search", 221 | "href": "https://leetcode.com/problems/search-in-rotated-sorted-array/", 222 | "text": "Search In Rotated Sorted Array", 223 | "difficulty": "Medium", 224 | "isPremium": false 225 | }, 226 | { 227 | "category": "Binary Search", 228 | "href": "https://leetcode.com/problems/time-based-key-value-store/", 229 | "text": "Time Based Key Value Store", 230 | "difficulty": "Medium", 231 | "isPremium": false 232 | }, 233 | { 234 | "category": "Binary Search", 235 | "href": "https://leetcode.com/problems/median-of-two-sorted-arrays/", 236 | "text": "Median of Two Sorted Arrays", 237 | "difficulty": "Hard", 238 | "isPremium": false 239 | }, 240 | { 241 | "category": "Linked List", 242 | "href": "https://leetcode.com/problems/reverse-linked-list/", 243 | "text": "Reverse Linked List", 244 | "difficulty": "Easy", 245 | "isPremium": false 246 | }, 247 | { 248 | "category": "Linked List", 249 | "href": "https://leetcode.com/problems/merge-two-sorted-lists/", 250 | "text": "Merge Two Sorted Lists", 251 | "difficulty": "Easy", 252 | "isPremium": false 253 | }, 254 | { 255 | "category": "Linked List", 256 | "href": "https://leetcode.com/problems/reorder-list/", 257 | "text": "Reorder List", 258 | "difficulty": "Medium", 259 | "isPremium": false 260 | }, 261 | { 262 | "category": "Linked List", 263 | "href": "https://leetcode.com/problems/remove-nth-node-from-end-of-list/", 264 | "text": "Remove Nth Node From End of List", 265 | "difficulty": "Medium", 266 | "isPremium": false 267 | }, 268 | { 269 | "category": "Linked List", 270 | "href": "https://leetcode.com/problems/copy-list-with-random-pointer/", 271 | "text": "Copy List With Random Pointer", 272 | "difficulty": "Medium", 273 | "isPremium": false 274 | }, 275 | { 276 | "category": "Linked List", 277 | "href": "https://leetcode.com/problems/add-two-numbers/", 278 | "text": "Add Two Numbers", 279 | "difficulty": "Medium", 280 | "isPremium": false 281 | }, 282 | { 283 | "category": "Linked List", 284 | "href": "https://leetcode.com/problems/linked-list-cycle/", 285 | "text": "Linked List Cycle", 286 | "difficulty": "Easy", 287 | "isPremium": false 288 | }, 289 | { 290 | "category": "Linked List", 291 | "href": "https://leetcode.com/problems/find-the-duplicate-number/", 292 | "text": "Find The Duplicate Number", 293 | "difficulty": "Medium", 294 | "isPremium": false 295 | }, 296 | { 297 | "category": "Linked List", 298 | "href": "https://leetcode.com/problems/lru-cache/", 299 | "text": "LRU Cache", 300 | "difficulty": "Medium", 301 | "isPremium": false 302 | }, 303 | { 304 | "category": "Linked List", 305 | "href": "https://leetcode.com/problems/merge-k-sorted-lists/", 306 | "text": "Merge K Sorted Lists", 307 | "difficulty": "Hard", 308 | "isPremium": false 309 | }, 310 | { 311 | "category": "Linked List", 312 | "href": "https://leetcode.com/problems/reverse-nodes-in-k-group/", 313 | "text": "Reverse Nodes In K Group", 314 | "difficulty": "Hard", 315 | "isPremium": false 316 | }, 317 | { 318 | "category": "Trees", 319 | "href": "https://leetcode.com/problems/invert-binary-tree/", 320 | "text": "Invert Binary Tree", 321 | "difficulty": "Easy", 322 | "isPremium": false 323 | }, 324 | { 325 | "category": "Trees", 326 | "href": "https://leetcode.com/problems/maximum-depth-of-binary-tree/", 327 | "text": "Maximum Depth of Binary Tree", 328 | "difficulty": "Easy", 329 | "isPremium": false 330 | }, 331 | { 332 | "category": "Trees", 333 | "href": "https://leetcode.com/problems/diameter-of-binary-tree/", 334 | "text": "Diameter of Binary Tree", 335 | "difficulty": "Easy", 336 | "isPremium": false 337 | }, 338 | { 339 | "category": "Trees", 340 | "href": "https://leetcode.com/problems/balanced-binary-tree/", 341 | "text": "Balanced Binary Tree", 342 | "difficulty": "Easy", 343 | "isPremium": false 344 | }, 345 | { 346 | "category": "Trees", 347 | "href": "https://leetcode.com/problems/same-tree/", 348 | "text": "Same Tree", 349 | "difficulty": "Easy", 350 | "isPremium": false 351 | }, 352 | { 353 | "category": "Trees", 354 | "href": "https://leetcode.com/problems/subtree-of-another-tree/", 355 | "text": "Subtree of Another Tree", 356 | "difficulty": "Easy", 357 | "isPremium": false 358 | }, 359 | { 360 | "category": "Trees", 361 | "href": "https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/", 362 | "text": "Lowest Common Ancestor of a Binary Search Tree", 363 | "difficulty": "Medium", 364 | "isPremium": false 365 | }, 366 | { 367 | "category": "Trees", 368 | "href": "https://leetcode.com/problems/binary-tree-level-order-traversal/", 369 | "text": "Binary Tree Level Order Traversal", 370 | "difficulty": "Medium", 371 | "isPremium": false 372 | }, 373 | { 374 | "category": "Trees", 375 | "href": "https://leetcode.com/problems/binary-tree-right-side-view/", 376 | "text": "Binary Tree Right Side View", 377 | "difficulty": "Medium", 378 | "isPremium": false 379 | }, 380 | { 381 | "category": "Trees", 382 | "href": "https://leetcode.com/problems/count-good-nodes-in-binary-tree/", 383 | "text": "Count Good Nodes In Binary Tree", 384 | "difficulty": "Medium", 385 | "isPremium": false 386 | }, 387 | { 388 | "category": "Trees", 389 | "href": "https://leetcode.com/problems/validate-binary-search-tree/", 390 | "text": "Validate Binary Search Tree", 391 | "difficulty": "Medium", 392 | "isPremium": false 393 | }, 394 | { 395 | "category": "Trees", 396 | "href": "https://leetcode.com/problems/kth-smallest-element-in-a-bst/", 397 | "text": "Kth Smallest Element In a Bst", 398 | "difficulty": "Medium", 399 | "isPremium": false 400 | }, 401 | { 402 | "category": "Trees", 403 | "href": "https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/", 404 | "text": "Construct Binary Tree From Preorder And Inorder Traversal", 405 | "difficulty": "Medium", 406 | "isPremium": false 407 | }, 408 | { 409 | "category": "Trees", 410 | "href": "https://leetcode.com/problems/binary-tree-maximum-path-sum/", 411 | "text": "Binary Tree Maximum Path Sum", 412 | "difficulty": "Hard", 413 | "isPremium": false 414 | }, 415 | { 416 | "category": "Trees", 417 | "href": "https://leetcode.com/problems/serialize-and-deserialize-binary-tree/", 418 | "text": "Serialize And Deserialize Binary Tree", 419 | "difficulty": "Hard", 420 | "isPremium": false 421 | }, 422 | { 423 | "category": "Tries", 424 | "href": "https://leetcode.com/problems/implement-trie-prefix-tree/", 425 | "text": "Implement Trie Prefix Tree", 426 | "difficulty": "Medium", 427 | "isPremium": false 428 | }, 429 | { 430 | "category": "Tries", 431 | "href": "https://leetcode.com/problems/design-add-and-search-words-data-structure/", 432 | "text": "Design Add And Search Words Data Structure", 433 | "difficulty": "Medium", 434 | "isPremium": false 435 | }, 436 | { 437 | "category": "Tries", 438 | "href": "https://leetcode.com/problems/word-search-ii/", 439 | "text": "Word Search II", 440 | "difficulty": "Hard", 441 | "isPremium": false 442 | }, 443 | { 444 | "category": "Heap / Priority Queue", 445 | "href": "https://leetcode.com/problems/kth-largest-element-in-a-stream/", 446 | "text": "Kth Largest Element In a Stream", 447 | "difficulty": "Easy", 448 | "isPremium": false 449 | }, 450 | { 451 | "category": "Heap / Priority Queue", 452 | "href": "https://leetcode.com/problems/last-stone-weight/", 453 | "text": "Last Stone Weight", 454 | "difficulty": "Easy", 455 | "isPremium": false 456 | }, 457 | { 458 | "category": "Heap / Priority Queue", 459 | "href": "https://leetcode.com/problems/k-closest-points-to-origin/", 460 | "text": "K Closest Points to Origin", 461 | "difficulty": "Medium", 462 | "isPremium": false 463 | }, 464 | { 465 | "category": "Heap / Priority Queue", 466 | "href": "https://leetcode.com/problems/kth-largest-element-in-an-array/", 467 | "text": "Kth Largest Element In An Array", 468 | "difficulty": "Medium", 469 | "isPremium": false 470 | }, 471 | { 472 | "category": "Heap / Priority Queue", 473 | "href": "https://leetcode.com/problems/task-scheduler/", 474 | "text": "Task Scheduler", 475 | "difficulty": "Medium", 476 | "isPremium": false 477 | }, 478 | { 479 | "category": "Heap / Priority Queue", 480 | "href": "https://leetcode.com/problems/design-twitter/", 481 | "text": "Design Twitter", 482 | "difficulty": "Medium", 483 | "isPremium": false 484 | }, 485 | { 486 | "category": "Heap / Priority Queue", 487 | "href": "https://leetcode.com/problems/find-median-from-data-stream/", 488 | "text": "Find Median From Data Stream", 489 | "difficulty": "Hard", 490 | "isPremium": false 491 | }, 492 | { 493 | "category": "Backtracking", 494 | "href": "https://leetcode.com/problems/subsets/", 495 | "text": "Subsets", 496 | "difficulty": "Medium", 497 | "isPremium": false 498 | }, 499 | { 500 | "category": "Backtracking", 501 | "href": "https://leetcode.com/problems/combination-sum/", 502 | "text": "Combination Sum", 503 | "difficulty": "Medium", 504 | "isPremium": false 505 | }, 506 | { 507 | "category": "Backtracking", 508 | "href": "https://leetcode.com/problems/permutations/", 509 | "text": "Permutations", 510 | "difficulty": "Medium", 511 | "isPremium": false 512 | }, 513 | { 514 | "category": "Backtracking", 515 | "href": "https://leetcode.com/problems/subsets-ii/", 516 | "text": "Subsets II", 517 | "difficulty": "Medium", 518 | "isPremium": false 519 | }, 520 | { 521 | "category": "Backtracking", 522 | "href": "https://leetcode.com/problems/combination-sum-ii/", 523 | "text": "Combination Sum II", 524 | "difficulty": "Medium", 525 | "isPremium": false 526 | }, 527 | { 528 | "category": "Backtracking", 529 | "href": "https://leetcode.com/problems/word-search/", 530 | "text": "Word Search", 531 | "difficulty": "Medium", 532 | "isPremium": false 533 | }, 534 | { 535 | "category": "Backtracking", 536 | "href": "https://leetcode.com/problems/palindrome-partitioning/", 537 | "text": "Palindrome Partitioning", 538 | "difficulty": "Medium", 539 | "isPremium": false 540 | }, 541 | { 542 | "category": "Backtracking", 543 | "href": "https://leetcode.com/problems/letter-combinations-of-a-phone-number/", 544 | "text": "Letter Combinations of a Phone Number", 545 | "difficulty": "Medium", 546 | "isPremium": false 547 | }, 548 | { 549 | "category": "Backtracking", 550 | "href": "https://leetcode.com/problems/n-queens/", 551 | "text": "N Queens", 552 | "difficulty": "Hard", 553 | "isPremium": false 554 | }, 555 | { 556 | "category": "Graphs", 557 | "href": "https://leetcode.com/problems/number-of-islands/", 558 | "text": "Number of Islands", 559 | "difficulty": "Medium", 560 | "isPremium": false 561 | }, 562 | { 563 | "category": "Graphs", 564 | "href": "https://leetcode.com/problems/clone-graph/", 565 | "text": "Clone Graph", 566 | "difficulty": "Medium", 567 | "isPremium": false 568 | }, 569 | { 570 | "category": "Graphs", 571 | "href": "https://leetcode.com/problems/max-area-of-island/", 572 | "text": "Max Area of Island", 573 | "difficulty": "Medium", 574 | "isPremium": false 575 | }, 576 | { 577 | "category": "Graphs", 578 | "href": "https://leetcode.com/problems/pacific-atlantic-water-flow/", 579 | "text": "Pacific Atlantic Water Flow", 580 | "difficulty": "Medium", 581 | "isPremium": false 582 | }, 583 | { 584 | "category": "Graphs", 585 | "href": "https://leetcode.com/problems/surrounded-regions/", 586 | "text": "Surrounded Regions", 587 | "difficulty": "Medium", 588 | "isPremium": false 589 | }, 590 | { 591 | "category": "Graphs", 592 | "href": "https://leetcode.com/problems/rotting-oranges/", 593 | "text": "Rotting Oranges", 594 | "difficulty": "Medium", 595 | "isPremium": false 596 | }, 597 | { 598 | "category": "Graphs", 599 | "href": "https://leetcode.com/problems/walls-and-gates/", 600 | "text": "Walls And Gates", 601 | "difficulty": "Medium", 602 | "isPremium": true 603 | }, 604 | { 605 | "category": "Graphs", 606 | "href": "https://leetcode.com/problems/course-schedule/", 607 | "text": "Course Schedule", 608 | "difficulty": "Medium", 609 | "isPremium": false 610 | }, 611 | { 612 | "category": "Graphs", 613 | "href": "https://leetcode.com/problems/course-schedule-ii/", 614 | "text": "Course Schedule II", 615 | "difficulty": "Medium", 616 | "isPremium": false 617 | }, 618 | { 619 | "category": "Graphs", 620 | "href": "https://leetcode.com/problems/redundant-connection/", 621 | "text": "Redundant Connection", 622 | "difficulty": "Medium", 623 | "isPremium": false 624 | }, 625 | { 626 | "category": "Graphs", 627 | "href": "https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/", 628 | "text": "Number of Connected Components In An Undirected Graph", 629 | "difficulty": "Medium", 630 | "isPremium": true 631 | }, 632 | { 633 | "category": "Graphs", 634 | "href": "https://leetcode.com/problems/graph-valid-tree/", 635 | "text": "Graph Valid Tree", 636 | "difficulty": "Medium", 637 | "isPremium": true 638 | }, 639 | { 640 | "category": "Graphs", 641 | "href": "https://leetcode.com/problems/word-ladder/", 642 | "text": "Word Ladder", 643 | "difficulty": "Hard", 644 | "isPremium": false 645 | }, 646 | { 647 | "category": "Advanced Graphs", 648 | "href": "https://leetcode.com/problems/reconstruct-itinerary/", 649 | "text": "Reconstruct Itinerary", 650 | "difficulty": "Hard", 651 | "isPremium": false 652 | }, 653 | { 654 | "category": "Advanced Graphs", 655 | "href": "https://leetcode.com/problems/min-cost-to-connect-all-points/", 656 | "text": "Min Cost to Connect All Points", 657 | "difficulty": "Medium", 658 | "isPremium": false 659 | }, 660 | { 661 | "category": "Advanced Graphs", 662 | "href": "https://leetcode.com/problems/network-delay-time/", 663 | "text": "Network Delay Time", 664 | "difficulty": "Medium", 665 | "isPremium": false 666 | }, 667 | { 668 | "category": "Advanced Graphs", 669 | "href": "https://leetcode.com/problems/swim-in-rising-water/", 670 | "text": "Swim In Rising Water", 671 | "difficulty": "Hard", 672 | "isPremium": false 673 | }, 674 | { 675 | "category": "Advanced Graphs", 676 | "href": "https://leetcode.com/problems/alien-dictionary/", 677 | "text": "Alien Dictionary", 678 | "difficulty": "Hard", 679 | "isPremium": true 680 | }, 681 | { 682 | "category": "Advanced Graphs", 683 | "href": "https://leetcode.com/problems/cheapest-flights-within-k-stops/", 684 | "text": "Cheapest Flights Within K Stops", 685 | "difficulty": "Medium", 686 | "isPremium": false 687 | }, 688 | { 689 | "category": "1-D Dynamic Programming", 690 | "href": "https://leetcode.com/problems/climbing-stairs/", 691 | "text": "Climbing Stairs", 692 | "difficulty": "Easy", 693 | "isPremium": false 694 | }, 695 | { 696 | "category": "1-D Dynamic Programming", 697 | "href": "https://leetcode.com/problems/min-cost-climbing-stairs/", 698 | "text": "Min Cost Climbing Stairs", 699 | "difficulty": "Easy", 700 | "isPremium": false 701 | }, 702 | { 703 | "category": "1-D Dynamic Programming", 704 | "href": "https://leetcode.com/problems/house-robber/", 705 | "text": "House Robber", 706 | "difficulty": "Medium", 707 | "isPremium": false 708 | }, 709 | { 710 | "category": "1-D Dynamic Programming", 711 | "href": "https://leetcode.com/problems/house-robber-ii/", 712 | "text": "House Robber II", 713 | "difficulty": "Medium", 714 | "isPremium": false 715 | }, 716 | { 717 | "category": "1-D Dynamic Programming", 718 | "href": "https://leetcode.com/problems/longest-palindromic-substring/", 719 | "text": "Longest Palindromic Substring", 720 | "difficulty": "Medium", 721 | "isPremium": false 722 | }, 723 | { 724 | "category": "1-D Dynamic Programming", 725 | "href": "https://leetcode.com/problems/palindromic-substrings/", 726 | "text": "Palindromic Substrings", 727 | "difficulty": "Medium", 728 | "isPremium": false 729 | }, 730 | { 731 | "category": "1-D Dynamic Programming", 732 | "href": "https://leetcode.com/problems/decode-ways/", 733 | "text": "Decode Ways", 734 | "difficulty": "Medium", 735 | "isPremium": false 736 | }, 737 | { 738 | "category": "1-D Dynamic Programming", 739 | "href": "https://leetcode.com/problems/coin-change/", 740 | "text": "Coin Change", 741 | "difficulty": "Medium", 742 | "isPremium": false 743 | }, 744 | { 745 | "category": "1-D Dynamic Programming", 746 | "href": "https://leetcode.com/problems/maximum-product-subarray/", 747 | "text": "Maximum Product Subarray", 748 | "difficulty": "Medium", 749 | "isPremium": false 750 | }, 751 | { 752 | "category": "1-D Dynamic Programming", 753 | "href": "https://leetcode.com/problems/word-break/", 754 | "text": "Word Break", 755 | "difficulty": "Medium", 756 | "isPremium": false 757 | }, 758 | { 759 | "category": "1-D Dynamic Programming", 760 | "href": "https://leetcode.com/problems/longest-increasing-subsequence/", 761 | "text": "Longest Increasing Subsequence", 762 | "difficulty": "Medium", 763 | "isPremium": false 764 | }, 765 | { 766 | "category": "1-D Dynamic Programming", 767 | "href": "https://leetcode.com/problems/partition-equal-subset-sum/", 768 | "text": "Partition Equal Subset Sum", 769 | "difficulty": "Medium", 770 | "isPremium": false 771 | }, 772 | { 773 | "category": "2-D Dynamic Programming", 774 | "href": "https://leetcode.com/problems/unique-paths/", 775 | "text": "Unique Paths", 776 | "difficulty": "Medium", 777 | "isPremium": false 778 | }, 779 | { 780 | "category": "2-D Dynamic Programming", 781 | "href": "https://leetcode.com/problems/longest-common-subsequence/", 782 | "text": "Longest Common Subsequence", 783 | "difficulty": "Medium", 784 | "isPremium": false 785 | }, 786 | { 787 | "category": "2-D Dynamic Programming", 788 | "href": "https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/", 789 | "text": "Best Time to Buy And Sell Stock With Cooldown", 790 | "difficulty": "Medium", 791 | "isPremium": false 792 | }, 793 | { 794 | "category": "2-D Dynamic Programming", 795 | "href": "https://leetcode.com/problems/coin-change-ii/", 796 | "text": "Coin Change II", 797 | "difficulty": "Medium", 798 | "isPremium": false 799 | }, 800 | { 801 | "category": "2-D Dynamic Programming", 802 | "href": "https://leetcode.com/problems/target-sum/", 803 | "text": "Target Sum", 804 | "difficulty": "Medium", 805 | "isPremium": false 806 | }, 807 | { 808 | "category": "2-D Dynamic Programming", 809 | "href": "https://leetcode.com/problems/interleaving-string/", 810 | "text": "Interleaving String", 811 | "difficulty": "Medium", 812 | "isPremium": false 813 | }, 814 | { 815 | "category": "2-D Dynamic Programming", 816 | "href": "https://leetcode.com/problems/longest-increasing-path-in-a-matrix/", 817 | "text": "Longest Increasing Path In a Matrix", 818 | "difficulty": "Hard", 819 | "isPremium": false 820 | }, 821 | { 822 | "category": "2-D Dynamic Programming", 823 | "href": "https://leetcode.com/problems/distinct-subsequences/", 824 | "text": "Distinct Subsequences", 825 | "difficulty": "Hard", 826 | "isPremium": false 827 | }, 828 | { 829 | "category": "2-D Dynamic Programming", 830 | "href": "https://leetcode.com/problems/edit-distance/", 831 | "text": "Edit Distance", 832 | "difficulty": "Medium", 833 | "isPremium": false 834 | }, 835 | { 836 | "category": "2-D Dynamic Programming", 837 | "href": "https://leetcode.com/problems/burst-balloons/", 838 | "text": "Burst Balloons", 839 | "difficulty": "Hard", 840 | "isPremium": false 841 | }, 842 | { 843 | "category": "2-D Dynamic Programming", 844 | "href": "https://leetcode.com/problems/regular-expression-matching/", 845 | "text": "Regular Expression Matching", 846 | "difficulty": "Hard", 847 | "isPremium": false 848 | }, 849 | { 850 | "category": "Greedy", 851 | "href": "https://leetcode.com/problems/maximum-subarray/", 852 | "text": "Maximum Subarray", 853 | "difficulty": "Medium", 854 | "isPremium": false 855 | }, 856 | { 857 | "category": "Greedy", 858 | "href": "https://leetcode.com/problems/jump-game/", 859 | "text": "Jump Game", 860 | "difficulty": "Medium", 861 | "isPremium": false 862 | }, 863 | { 864 | "category": "Greedy", 865 | "href": "https://leetcode.com/problems/jump-game-ii/", 866 | "text": "Jump Game II", 867 | "difficulty": "Medium", 868 | "isPremium": false 869 | }, 870 | { 871 | "category": "Greedy", 872 | "href": "https://leetcode.com/problems/gas-station/", 873 | "text": "Gas Station", 874 | "difficulty": "Medium", 875 | "isPremium": false 876 | }, 877 | { 878 | "category": "Greedy", 879 | "href": "https://leetcode.com/problems/hand-of-straights/", 880 | "text": "Hand of Straights", 881 | "difficulty": "Medium", 882 | "isPremium": false 883 | }, 884 | { 885 | "category": "Greedy", 886 | "href": "https://leetcode.com/problems/merge-triplets-to-form-target-triplet/", 887 | "text": "Merge Triplets to Form Target Triplet", 888 | "difficulty": "Medium", 889 | "isPremium": false 890 | }, 891 | { 892 | "category": "Greedy", 893 | "href": "https://leetcode.com/problems/partition-labels/", 894 | "text": "Partition Labels", 895 | "difficulty": "Medium", 896 | "isPremium": false 897 | }, 898 | { 899 | "category": "Greedy", 900 | "href": "https://leetcode.com/problems/valid-parenthesis-string/", 901 | "text": "Valid Parenthesis String", 902 | "difficulty": "Medium", 903 | "isPremium": false 904 | }, 905 | { 906 | "category": "Intervals", 907 | "href": "https://leetcode.com/problems/insert-interval/", 908 | "text": "Insert Interval", 909 | "difficulty": "Medium", 910 | "isPremium": false 911 | }, 912 | { 913 | "category": "Intervals", 914 | "href": "https://leetcode.com/problems/merge-intervals/", 915 | "text": "Merge Intervals", 916 | "difficulty": "Medium", 917 | "isPremium": false 918 | }, 919 | { 920 | "category": "Intervals", 921 | "href": "https://leetcode.com/problems/non-overlapping-intervals/", 922 | "text": "Non Overlapping Intervals", 923 | "difficulty": "Medium", 924 | "isPremium": false 925 | }, 926 | { 927 | "category": "Intervals", 928 | "href": "https://leetcode.com/problems/meeting-rooms/", 929 | "text": "Meeting Rooms", 930 | "difficulty": "Easy", 931 | "isPremium": true 932 | }, 933 | { 934 | "category": "Intervals", 935 | "href": "https://leetcode.com/problems/meeting-rooms-ii/", 936 | "text": "Meeting Rooms II", 937 | "difficulty": "Medium", 938 | "isPremium": true 939 | }, 940 | { 941 | "category": "Intervals", 942 | "href": "https://leetcode.com/problems/minimum-interval-to-include-each-query/", 943 | "text": "Minimum Interval to Include Each Query", 944 | "difficulty": "Hard", 945 | "isPremium": false 946 | }, 947 | { 948 | "category": "Math & Geometry", 949 | "href": "https://leetcode.com/problems/rotate-image/", 950 | "text": "Rotate Image", 951 | "difficulty": "Medium", 952 | "isPremium": false 953 | }, 954 | { 955 | "category": "Math & Geometry", 956 | "href": "https://leetcode.com/problems/spiral-matrix/", 957 | "text": "Spiral Matrix", 958 | "difficulty": "Medium", 959 | "isPremium": false 960 | }, 961 | { 962 | "category": "Math & Geometry", 963 | "href": "https://leetcode.com/problems/set-matrix-zeroes/", 964 | "text": "Set Matrix Zeroes", 965 | "difficulty": "Medium", 966 | "isPremium": false 967 | }, 968 | { 969 | "category": "Math & Geometry", 970 | "href": "https://leetcode.com/problems/happy-number/", 971 | "text": "Happy Number", 972 | "difficulty": "Easy", 973 | "isPremium": false 974 | }, 975 | { 976 | "category": "Math & Geometry", 977 | "href": "https://leetcode.com/problems/plus-one/", 978 | "text": "Plus One", 979 | "difficulty": "Easy", 980 | "isPremium": false 981 | }, 982 | { 983 | "category": "Math & Geometry", 984 | "href": "https://leetcode.com/problems/powx-n/", 985 | "text": "Pow(x, n)", 986 | "difficulty": "Medium", 987 | "isPremium": false 988 | }, 989 | { 990 | "category": "Math & Geometry", 991 | "href": "https://leetcode.com/problems/multiply-strings/", 992 | "text": "Multiply Strings", 993 | "difficulty": "Medium", 994 | "isPremium": false 995 | }, 996 | { 997 | "category": "Math & Geometry", 998 | "href": "https://leetcode.com/problems/detect-squares/", 999 | "text": "Detect Squares", 1000 | "difficulty": "Medium", 1001 | "isPremium": false 1002 | }, 1003 | { 1004 | "category": "Bit Manipulation", 1005 | "href": "https://leetcode.com/problems/single-number/", 1006 | "text": "Single Number", 1007 | "difficulty": "Easy", 1008 | "isPremium": false 1009 | }, 1010 | { 1011 | "category": "Bit Manipulation", 1012 | "href": "https://leetcode.com/problems/number-of-1-bits/", 1013 | "text": "Number of 1 Bits", 1014 | "difficulty": "Easy", 1015 | "isPremium": false 1016 | }, 1017 | { 1018 | "category": "Bit Manipulation", 1019 | "href": "https://leetcode.com/problems/counting-bits/", 1020 | "text": "Counting Bits", 1021 | "difficulty": "Easy", 1022 | "isPremium": false 1023 | }, 1024 | { 1025 | "category": "Bit Manipulation", 1026 | "href": "https://leetcode.com/problems/reverse-bits/", 1027 | "text": "Reverse Bits", 1028 | "difficulty": "Easy", 1029 | "isPremium": false 1030 | }, 1031 | { 1032 | "category": "Bit Manipulation", 1033 | "href": "https://leetcode.com/problems/missing-number/", 1034 | "text": "Missing Number", 1035 | "difficulty": "Easy", 1036 | "isPremium": false 1037 | }, 1038 | { 1039 | "category": "Bit Manipulation", 1040 | "href": "https://leetcode.com/problems/sum-of-two-integers/", 1041 | "text": "Sum of Two Integers", 1042 | "difficulty": "Medium", 1043 | "isPremium": false 1044 | }, 1045 | { 1046 | "category": "Bit Manipulation", 1047 | "href": "https://leetcode.com/problems/reverse-integer/", 1048 | "text": "Reverse Integer", 1049 | "difficulty": "Medium", 1050 | "isPremium": false 1051 | } 1052 | ] 1053 | -------------------------------------------------------------------------------- /leetcodeProblems.ts: -------------------------------------------------------------------------------- 1 | import { storage } from "storage" 2 | import type { APILeetCodeProblem, JSONLeetCodeProblem } from "types" 3 | 4 | import { getProblemListFromLeetCodeAPI } from "~background" 5 | 6 | export async function getAllLeetCodeProblems( 7 | difficulty: string, 8 | problemSet: string 9 | ) { 10 | try { 11 | // Remove lg- or all from string for better logic processing 12 | const leetCodeProblems: APILeetCodeProblem[] = 13 | await getProblemListFromLeetCodeAPI( 14 | difficulty, 15 | problemSet?.slice(3) || "" 16 | ) 17 | let randomIndex = Math.floor(Math.random() * leetCodeProblems.length) 18 | while (leetCodeProblems[randomIndex].paidOnly) { 19 | randomIndex++ 20 | randomIndex = 21 | (leetCodeProblems.length + randomIndex) % leetCodeProblems.length 22 | } 23 | const randomProblem = leetCodeProblems[randomIndex] 24 | // Replace anything that is not a string or whitespace with "" then replace empty spaces with "-" 25 | //Error with some problems with special characters TODO: Fix this ex: nondrecreasing subequence -> non-decreasing-subsequence 26 | const randomProblemURL = 27 | "https://leetcode.com/problems/" + randomProblem.titleSlug + "/" 28 | const randomProblemName = randomProblem.title 29 | // await storage.set("loading", false) 30 | await storage.stopLoading() 31 | return { url: randomProblemURL, name: randomProblemName } 32 | } catch (error) { 33 | console.error(error) 34 | } 35 | } 36 | 37 | export async function getLeetCodeProblemFromProblemSet( 38 | difficulty: string, 39 | problemSet: string 40 | ) { 41 | try { 42 | const includePremium = await storage.getIncludePremium() 43 | // TODO: Need to find a way to filter out premium problems for these JSON files 44 | const problemSetURLs: Record = { 45 | allNeetcode: "leetcode-problems/allProblems.json", 46 | NeetCode150: "leetcode-problems/neetCode150Problems.json", 47 | Blind75: "leetcode-problems/blind75Problems.json", 48 | metaTop100: "leetcode-problems/metaTop100.json" 49 | } 50 | 51 | const res = await fetch(chrome.runtime.getURL(problemSetURLs[problemSet])) 52 | const leetCodeProblems: JSONLeetCodeProblem[] = await res.json() 53 | const filteredLeetCodeProblems: JSONLeetCodeProblem[] = 54 | leetCodeProblems.filter((problem) => { 55 | if (!problem.href.endsWith("/")) { 56 | problem.href += "/" 57 | } 58 | return ( 59 | (includePremium || !problem.isPremium) && 60 | (difficulty == "all" || 61 | problem.difficulty.toLowerCase() === difficulty.toLowerCase()) 62 | ) 63 | }) 64 | 65 | let randomIndex = Math.floor( 66 | Math.random() * filteredLeetCodeProblems.length 67 | ) 68 | const randomProblem = filteredLeetCodeProblems[randomIndex] 69 | const randomProblemURL = randomProblem.href 70 | const randomProblemName = randomProblem.text 71 | return { url: randomProblemURL, name: randomProblemName } 72 | } catch (error) { 73 | console.error(error) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode-torture", 3 | "displayName": "Leetcode torture", 4 | "version": "0.0.1", 5 | "description": "An extension to torture you with LeetCode problems and force you to solve them.", 6 | "author": "The Coding Sloth", 7 | "scripts": { 8 | "dev": "plasmo dev", 9 | "build": "plasmo build", 10 | "dev-firefox": "plasmo dev --target=firefox-mv3", 11 | "build-firefox": "plasmo build --target=firefox-mv3", 12 | "test": "plasmo test", 13 | "web-scrape-tests": "jest --runInBand" 14 | }, 15 | "dependencies": { 16 | "@plasmohq/storage": "^1.9.3", 17 | "plasmo": "0.85.2", 18 | "puppeteer": "^22.6.3", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "uuid": "^9.0.1" 22 | }, 23 | "devDependencies": { 24 | "@ianvs/prettier-plugin-sort-imports": "4.2.1", 25 | "@types/chrome": "0.0.265", 26 | "@types/node": "20.12.5", 27 | "@types/react": "18.2.74", 28 | "@types/react-dom": "18.2.24", 29 | "@typescript-eslint/eslint-plugin": "^7.5.0", 30 | "eslint": "^9.0.0", 31 | "eslint-plugin-import": "^2.29.1", 32 | "eslint-plugin-n": "^16.6.2", 33 | "eslint-plugin-promise": "^6.1.1", 34 | "eslint-plugin-react": "^7.34.1", 35 | "jest": "^29.7.0", 36 | "jest-puppeteer": "^10.0.1", 37 | "prettier": "3.2.5", 38 | "typescript": "5.4.4" 39 | }, 40 | "manifest": { 41 | "browser_specific_settings": { 42 | "gecko": { 43 | "id": "{743ff2c1-74c2-4384-a218-2c33d6a765a5}" 44 | } 45 | }, 46 | "permissions": [ 47 | "declarativeNetRequest", 48 | "declarativeNetRequestFeedback", 49 | "storage", 50 | "tabs", 51 | "activeTab", 52 | "alarms", 53 | "webRequest" 54 | ], 55 | "web_accessible_resources": [ 56 | { 57 | "resources": [ 58 | "leetcode-problems/*" 59 | ], 60 | "matches": [ 61 | "https://www.plasmo.com/*" 62 | ] 63 | } 64 | ], 65 | "host_permissions": [ 66 | "" 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /popup.tsx: -------------------------------------------------------------------------------- 1 | import "styles.css" 2 | 3 | import SettingDrawer from "components/SettingDrawer" 4 | import { useEffect, useState } from "react" 5 | 6 | import { useStorage } from "@plasmohq/storage/hook" 7 | 8 | import HyperTortureMode from "~components/HyperTortureMode" 9 | import NoPermissions from "~components/NoPermissions" 10 | import SettingsIcon from "~components/SettingsIcon" 11 | 12 | import { 13 | generateRandomLeetCodeProblem, 14 | handleAdditionalProblemRedirect, 15 | updateProblemState 16 | } from "./background" 17 | 18 | const IndexPopup = () => { 19 | const unSolvedMessages = [ 20 | "Another day, another LeetCode problem, so go solve it buddy", 21 | "One LeetCode problem a day keeps the unemployment away", 22 | "Welcome to your daily dose of LeetCode", 23 | "Never back down, Never what" 24 | ] 25 | const solvedMessages = [ 26 | "Bro you only solved one problem, chill out", 27 | "You survived another day of LeetCode, congrats", 28 | "You're one step closer to getting that job, keep it up", 29 | "The LeetCode Torture gods are pleased. Rest, for tomorrow brings a new challenge", 30 | "Solved your problem for the day, nice, go treat yourself" 31 | ] 32 | const hyperTortureMessages = [ 33 | "Your code is compiling... just kidding, prepare for eternal agony.", 34 | "Infinite loop of despair activated.", 35 | "Feel the burn(out), keep those functions running.", 36 | "Error 404: Social life not found. Keep coding.", 37 | "Another day, another dollar... subtracted from your sanity budget.", 38 | "Commit to the code grind, the keyboard is your only friend." 39 | ] 40 | const [unsolvedMessage, setUnsolvedMessage] = useState("") 41 | const [solvedMessage, setSolvedMessage] = useState("") 42 | const [hyperTortureMessage, setHyperTortureMessage] = useState("") 43 | const [problemName] = useStorage("problemName") 44 | const [problemURL] = useStorage("problemURL") 45 | const [leetcodeProblemSolved] = useStorage("leetCodeProblemSolved") 46 | const [hyperTortureMode] = useStorage("hyperTortureMode") 47 | const [currentStreak] = useStorage("currentStreak") 48 | const [bestStreak] = useStorage("bestStreak") 49 | const [HT_currentStreak] = useStorage("HT_currentStreak") 50 | const [HT_bestStreak] = useStorage("HT_bestStreak") 51 | const [permissionsEnabled] = useStorage("permissionsEnabled", true) 52 | const [drawerClosed, setDrawerClosed] = useState(true) 53 | 54 | const getRandomInt = (max: number) => Math.floor(Math.random() * max) 55 | 56 | const solveAnother = async () => { 57 | const problem = await generateRandomLeetCodeProblem() 58 | await updateProblemState(problem) 59 | await handleAdditionalProblemRedirect(problem.url) 60 | } 61 | 62 | useEffect(() => { 63 | const unsolvedIndex = getRandomInt(unSolvedMessages.length) 64 | const hyperTortureIndex = getRandomInt(hyperTortureMessages.length) 65 | const randomSolvedIndex = getRandomInt(solvedMessages.length) 66 | 67 | setSolvedMessage(solvedMessages[randomSolvedIndex]) 68 | setHyperTortureMessage(hyperTortureMessages[hyperTortureIndex]) 69 | setUnsolvedMessage(unSolvedMessages[unsolvedIndex]) 70 | }, []) 71 | 72 | if (!permissionsEnabled) { 73 | return 74 | } 75 | 76 | return ( 77 |
78 | 84 | 85 | {!problemName && ( 86 |
87 |

Fetching torture problem...

88 | 89 |
90 | )} 91 | 92 | {hyperTortureMode && ( 93 | 97 | )} 98 | 99 | {!leetcodeProblemSolved && !hyperTortureMode && ( 100 | <> 101 |

{unsolvedMessage}

102 | 103 |
104 |

Today's Question

105 |

{problemName}

106 | 111 |
112 | 113 | )} 114 | {leetcodeProblemSolved && ( 115 |
116 |

{solvedMessage}

117 | 120 |
121 | )} 122 |
123 |
124 |

Current Streak:

125 |

{hyperTortureMode ? HT_currentStreak ?? 0 : currentStreak ?? 0}

126 |
127 |
128 |

Best Streak:

129 |

{hyperTortureMode ? HT_bestStreak ?? 0 : bestStreak ?? 0}

130 |
131 |
132 | 133 | 134 |
135 | ) 136 | } 137 | 138 | export default IndexPopup 139 | -------------------------------------------------------------------------------- /storage.ts: -------------------------------------------------------------------------------- 1 | import { Storage } from "@plasmohq/storage" 2 | 3 | const storage = new Storage() 4 | 5 | export const getProblemUrl = async () => await storage.get("problemURL") 6 | export const getProblemSet = async () => 7 | (await storage.get("problemSets")) ?? "all" 8 | export const getDifficulty = async () => 9 | (await storage.get("difficulty")) ?? "all" 10 | export const getIncludePremium = async () => 11 | Boolean(await storage.get("includePremium")) ?? false 12 | export const getProblemSolved = async () => 13 | Boolean(await storage.get("leetCodeProblemSolved")) ?? false 14 | export const initiateLoading = async () => await storage.set("loading", true) 15 | export const stopLoading = async () => await storage.set("loading", false) 16 | export const getHyperTortureMode = async () => 17 | !!(await storage.get("hyperTortureMode")) 18 | export const getEnableRedirectOnEveryProblem = async () => 19 | !(await storage.get("enableRedirectOnEveryProblem")) 20 | 21 | export async function updateProblem( 22 | problem: { url: string; name: string }, 23 | isSolved: boolean 24 | ) { 25 | return Promise.all([ 26 | storage.set("problemURL", problem.url), 27 | storage.set("problemName", problem.name), 28 | storage.set("problemDate", new Date().toDateString()), 29 | updateProblemSolvedState(isSolved) 30 | ]) 31 | } 32 | 33 | export async function updateEnableRedirectOnEveryProblem(enabled: boolean) { 34 | await storage.set("enableRedirectOnEveryProblem", enabled) 35 | } 36 | 37 | export async function updatePermissions(enabled: boolean) { 38 | await storage.set("permissionsEnabled", enabled) 39 | } 40 | 41 | // TODO: Maybe this needs to be exported for clarity (instead of being used in updateProblem and updateStreak) 42 | async function updateProblemSolvedState(isSolved: boolean) { 43 | await storage.set("leetCodeProblemSolved", isSolved) 44 | } 45 | 46 | export async function getLastCompletion() { 47 | const lastCompletedString = await storage.get("lastCompleted") 48 | // Returns Unix Epoch if item is null 49 | return lastCompletedString ? new Date(lastCompletedString) : new Date(0) 50 | } 51 | 52 | export async function updateStreak() { 53 | if (await getHyperTortureMode()) { 54 | // Update hyper torture streak 55 | const [HT_bestStreak, HT_currentStreak] = await Promise.all([ 56 | storage.get("HT_bestStreak"), 57 | storage.get("HT_currentStreak") 58 | ]) 59 | 60 | const HT_newStreak = (Number(HT_currentStreak) || 0) + 1 61 | const HT_best = Number(HT_bestStreak) || 0 62 | 63 | await storage.set("HT_currentStreak", HT_newStreak) 64 | // If new hyper torture streak higher than best hyper torture streak, update it 65 | if (HT_newStreak > HT_best) await storage.set("HT_bestStreak", HT_newStreak) 66 | } else { 67 | const [_, lastCompletion] = await Promise.all([ 68 | updateProblemSolvedState(true), 69 | getLastCompletion() 70 | ]) 71 | 72 | const now = new Date() 73 | if (lastCompletion.toDateString() === new Date().toDateString()) return 74 | 75 | const [bestStreak, currentStreak] = await Promise.all([ 76 | storage.get("bestStreak"), 77 | storage.get("currentStreak") 78 | ]) 79 | 80 | const newStreak = (Number(currentStreak) || 0) + 1 81 | const best = Number(bestStreak) || 0 82 | 83 | await storage.set("currentStreak", newStreak) 84 | await storage.set("lastCompleted", now.toDateString()) 85 | if (newStreak > best) await storage.set("bestStreak", newStreak) 86 | } 87 | } 88 | 89 | export async function resetStreak() { 90 | await storage.set("currentStreak", 0) 91 | } 92 | 93 | export async function resetHyperTortureStreak() { 94 | await storage.set("HT_currentStreak", 0) 95 | } 96 | 97 | export * as storage from "storage" 98 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Arial, Helvetica, sans-serif; 5 | } 6 | body { 7 | color: white; 8 | /* Adjusted for better visibility */ 9 | /* box-shadow: 0px 5px 20px 2px rgb(0 0 0 / 0.35); */ 10 | background-color: rgb(21, 21, 30); 11 | } 12 | .popup { 13 | width: 25rem; 14 | min-height: 20rem; 15 | transition: all 0.3s ease-in-out; 16 | overflow: hidden; 17 | } 18 | 19 | .popup.settings { 20 | width: 25rem; 21 | min-height: 25rem; 22 | } 23 | 24 | nav { 25 | background-color: #4f46e5; 26 | height: 2em; 27 | display: flex; 28 | flex-direction: row; 29 | gap: 0.2rem; 30 | align-items: center; 31 | margin-bottom: 0.5rem; 32 | padding: 0.5rem; 33 | } 34 | 35 | nav h1 { 36 | font-size: 1.2rem; 37 | padding: 0; 38 | margin: 0; 39 | line-height: 1em; 40 | font-weight: bold; 41 | } 42 | 43 | nav button { 44 | font-size: 16px; 45 | outline: 0; 46 | border: 0; 47 | background-color: transparent; 48 | height: inherit; 49 | width: inherit; 50 | margin: 0; 51 | padding: 0; 52 | cursor: pointer; 53 | } 54 | 55 | nav .flex { 56 | flex: 1; 57 | } 58 | 59 | input[type="number"]::-webkit-inner-spin-button, 60 | input[type="number"]::-webkit-outer-spin-button { 61 | -webkit-appearance: none; 62 | appearance: none; 63 | margin: 0; 64 | } 65 | 66 | .title { 67 | font-size: 1.3rem; 68 | text-align: center; 69 | padding: 0.5rem; 70 | background-color: rgb(87, 74, 139); 71 | } 72 | 73 | #unsolved-message, 74 | #solved-message, 75 | #hyperTorture-message { 76 | text-align: center; 77 | padding: 0.5rem; 78 | } 79 | 80 | #unsolved-message { 81 | font-size: 1rem; 82 | margin-bottom: 0.5rem; 83 | } 84 | #solved-message { 85 | font-size: 1.5rem; 86 | } 87 | 88 | .leetcode-info { 89 | display: flex; 90 | flex-direction: column; 91 | justify-content: center; 92 | align-items: center; 93 | gap: 0.5rem; 94 | } 95 | 96 | #question-of-day-msg { 97 | font-size: 1rem; 98 | } 99 | 100 | #leetcode-problem-name { 101 | font-size: 1.3rem; 102 | font-weight: bold; 103 | text-decoration: none; 104 | line-height: 1; 105 | text-align: center; 106 | width: 100%; 107 | margin-bottom: 0.5rem; 108 | } 109 | 110 | #leetcode-problem-button { 111 | background-color: #4f46e5; 112 | font-size: 1rem; 113 | border: none; 114 | width: 50%; 115 | border-radius: 10px; 116 | padding: 0.75rem; 117 | color: white; 118 | font-weight: 600; 119 | } 120 | 121 | #leetcode-problem-button:hover { 122 | cursor: pointer; 123 | background-color: #3c34b5; 124 | } 125 | 126 | #difficulty-selection { 127 | display: flex; 128 | font-size: 1.2em; 129 | flex-direction: row; 130 | align-items: center; 131 | width: 70%; 132 | margin: 1em auto 0 auto; 133 | } 134 | 135 | #difficulty-selection p { 136 | padding-right: 1em; 137 | } 138 | 139 | #difficulty-selection select { 140 | background-color: transparent; 141 | border: 1px solid gray; 142 | padding: 0.5em; 143 | color: white; 144 | border-radius: 0.3em; 145 | flex: 1; 146 | outline: 0; 147 | cursor: pointer; 148 | } 149 | 150 | #difficulty-selection select:hover { 151 | outline: 1px solid #ffffff80; 152 | } 153 | 154 | #difficulty-selection select option { 155 | color: black; 156 | } 157 | #current-streak-message, 158 | #best-streak-message { 159 | font-size: 1.1rem; 160 | text-align: center; 161 | padding: 0.5rem; 162 | } 163 | 164 | .loader { 165 | width: 48px; 166 | height: 48px; 167 | border: 5px solid #fff; 168 | border-bottom-color: #4f46e5; 169 | border-radius: 50%; 170 | display: inline-block; 171 | box-sizing: border-box; 172 | animation: rotation 1s linear infinite; 173 | } 174 | 175 | @keyframes rotation { 176 | 0% { 177 | transform: rotate(0deg); 178 | } 179 | 100% { 180 | transform: rotate(360deg); 181 | } 182 | } 183 | .loading { 184 | display: flex; 185 | flex-direction: column; 186 | justify-content: center; 187 | align-items: center; 188 | padding: 2rem 0 2rem 0; 189 | height: 100%; 190 | font-size: 1.2rem; 191 | font-weight: 700; 192 | gap: 1rem; 193 | } 194 | .drawer { 195 | position: fixed; 196 | top: 0; 197 | right: -100%; 198 | width: 100%; 199 | height: 100%; 200 | overflow-y: auto; 201 | background-color: #fff; 202 | transition: right 0.3s ease-in-out; 203 | } 204 | 205 | .drawer.opened { 206 | right: 0; 207 | } 208 | 209 | .drawer .setting-labels { 210 | background-color: #15151e; 211 | height: 100%; 212 | font-size: 1em; 213 | padding: 1rem; 214 | list-style: none; 215 | } 216 | 217 | .setting-label { 218 | display: flex; 219 | flex-direction: row; 220 | align-items: top; 221 | } 222 | 223 | .setting-labels li:nth-of-type(n + 2) { 224 | padding-top: 1rem; 225 | } 226 | 227 | .setting-labels .settings-problem-solved { 228 | font-size: 0.8rem; 229 | text-align: center; 230 | padding: 0 1rem 1.5rem 1rem; 231 | font-weight: 700; 232 | } 233 | .setting-label p.name { 234 | font-size: 1rem; 235 | font-weight: 700; 236 | padding-bottom: 0.2em; 237 | } 238 | .setting-label p.description { 239 | font-weight: 700; 240 | font-size: 0.8rem; 241 | color: #afaaaa; 242 | } 243 | 244 | .setting-label .setting-text { 245 | flex: 1; 246 | } 247 | 248 | .setting-label .setting-dropdown { 249 | background-color: transparent; 250 | color: #ffffff; 251 | outline: 0; 252 | border: 0; 253 | font-size: 1rem; 254 | font-weight: 700; 255 | cursor: pointer; 256 | } 257 | 258 | .setting-label .setting-dropdown option { 259 | background-color: #4f46e5; 260 | } 261 | 262 | .setting-label .setting-input { 263 | height: 1.3em; 264 | padding: 0.2em; 265 | width: 8em; 266 | } 267 | 268 | .permissions-warning { 269 | display: flex; 270 | flex-direction: column; 271 | justify-content: center; 272 | align-items: center; 273 | gap: 1rem; 274 | font-size: 0.8rem; 275 | text-align: center; 276 | padding: 1rem; 277 | font-weight: 700; 278 | } 279 | 280 | .checking-permissions { 281 | display: flex; 282 | flex: 1; 283 | flex-direction: column; 284 | justify-content: center; 285 | align-items: center; 286 | gap: 1rem; 287 | font-size: 0.8rem; 288 | text-align: center; 289 | padding: 1rem; 290 | font-weight: 700; 291 | } 292 | 293 | .checking-permissions p { 294 | font-size: 1rem; 295 | font-weight: 700; 296 | } 297 | 298 | .streak-section { 299 | display: flex; 300 | justify-content: space-between; 301 | flex: 1; 302 | align-items: center; 303 | gap: 1rem; 304 | font-size: 0.8rem; 305 | padding: 1rem; 306 | font-weight: 700; 307 | } 308 | 309 | .current-streak { 310 | display: flex; 311 | flex-direction: column; 312 | justify-content: center; 313 | align-items: center; 314 | } 315 | 316 | .current-streak p { 317 | font-size: 1.5rem; 318 | font-weight: 700; 319 | } 320 | 321 | .solved-section { 322 | display: flex; 323 | justify-content: center; 324 | flex-direction: column; 325 | align-items: center; 326 | flex: 1; 327 | gap: 1rem; 328 | flex: 1; 329 | font-size: 0.8rem; 330 | padding: 1rem; 331 | font-weight: 700; 332 | } 333 | -------------------------------------------------------------------------------- /testDirectory/testFile.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "value" 3 | } -------------------------------------------------------------------------------- /tests/01_savetoJSON.test.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const path = require("path") 3 | const { saveProblemstoJSON } = require("../web_scraper/web_scrape_problems") 4 | const uuidv4 = require("uuid").v4 5 | 6 | const testDirectory = "../testDirectory" 7 | const testFilename = "testFile.json" 8 | const testData = { key: "value" } 9 | 10 | describe("saveProblemstoJSON function", () => { 11 | let uniqueTestDirectory 12 | const getFilePath = () => path.join(testDirectory, testFilename) 13 | // Setup: Create unique directory for tests 14 | beforeAll(() => { 15 | uniqueTestDirectory = path.join(testDirectory, uuidv4()) 16 | console.log("Before attemping tests, deleting directory: ", testDirectory) 17 | if (fs.existsSync(testDirectory)) { 18 | fs.rmSync(testDirectory, { recursive: true }) // Using recursive to ensure it deletes non-empty directories 19 | } 20 | }) 21 | 22 | // Cleanup: Delete test file after each test 23 | afterEach(() => { 24 | const filePath = getFilePath() 25 | if (fs.existsSync(filePath)) { 26 | fs.unlinkSync(filePath) 27 | } 28 | }) 29 | 30 | // Cleanup: Delete unique test directory after all tests 31 | afterAll(() => { 32 | console.log("attempting to delete directory: ", testDirectory) 33 | if (fs.existsSync(testDirectory)) { 34 | fs.rmSync(testDirectory, { recursive: true }) // Using recursive to ensure it deletes non-empty directories 35 | } 36 | }) 37 | 38 | it("should create a new directory if it does not exist", () => { 39 | expect(fs.existsSync(testDirectory)).toBe(false) 40 | saveProblemstoJSON(testFilename, testDirectory, testData) 41 | expect(fs.existsSync(testDirectory)).toBe(true) 42 | }) 43 | 44 | it("should create a file with the given filename", () => { 45 | saveProblemstoJSON(testFilename, testDirectory, testData) 46 | const filePath = getFilePath() 47 | expect(fs.existsSync(filePath)).toBe(true) 48 | }) 49 | 50 | it("should write correct data to the file", () => { 51 | saveProblemstoJSON(testFilename, testDirectory, testData) 52 | const filePath = getFilePath() 53 | const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) 54 | expect(content).toEqual(testData) 55 | }) 56 | 57 | it("should overwrite existing file with new data", () => { 58 | const initialData = { initial: "data" } 59 | const filePath = getFilePath() 60 | saveProblemstoJSON(testFilename, testDirectory, initialData) 61 | saveProblemstoJSON(testFilename, testDirectory, testData) 62 | const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) 63 | expect(content).not.toEqual(initialData) 64 | expect(content).toEqual(testData) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /tests/02_scraper.test.js: -------------------------------------------------------------------------------- 1 | //TODO: fix these stupid tests so that they can run together properly 2 | //For some reason, when you run them together, the scraper passes, but not the savetoJSON???? 3 | 4 | const { 5 | initiatePuppeteer, 6 | scrapeBlind75Problems, 7 | scrapeNeetCode150Problems, 8 | scrapeAllProblems, 9 | scrapeCategories, 10 | saveProblemstoJSON 11 | } = require("../web_scraper/web_scrape_problems") 12 | 13 | const fs = require("fs") 14 | const path = require("path") 15 | const uuidv4 = require("uuid").v4 16 | 17 | // Variables for Puppeteer 18 | let page, browser 19 | 20 | // Add or remove categories as needed 21 | // Might need to find a better way to scrape the categories, since this is highly dependent on the website too, so these tests might break 22 | const currentCategories = [ 23 | "Arrays & Hashing", 24 | "Two Pointers", 25 | "Sliding Window", 26 | "Stack", 27 | "Binary Search", 28 | "Linked List", 29 | "Trees", 30 | "Tries", 31 | "Heap / Priority Queue", 32 | "Backtracking", 33 | "Graphs", 34 | "Advanced Graphs", 35 | "1-D Dynamic Programming", 36 | "2-D Dynamic Programming", 37 | "Greedy", 38 | "Intervals", 39 | "Math & Geometry", 40 | "Bit Manipulation", 41 | "JavaScript" 42 | ] 43 | 44 | const testDirectory = "../testLeetcode-Problems" 45 | let uniqueTestDirectory 46 | 47 | const removeFiles = () => { 48 | // Define filePath for each file to be deleted 49 | const allProblemsFilePath = path.join( 50 | __dirname, 51 | `${testDirectory}/allProblems.json` 52 | ) 53 | const blind75ProblemsFilePath = path.join( 54 | __dirname, 55 | `${testDirectory}/blind75Problems.json` 56 | ) 57 | const neetCode150ProblemsFilePath = path.join( 58 | __dirname, 59 | `${testDirectory}/neetCode150Problems.json` 60 | ) 61 | 62 | // Delete allProblems.json if it exists 63 | if (fs.existsSync(allProblemsFilePath)) { 64 | fs.unlinkSync(allProblemsFilePath) 65 | } 66 | 67 | // Delete blind75Problems.json if it exists 68 | if (fs.existsSync(blind75ProblemsFilePath)) { 69 | fs.unlinkSync(blind75ProblemsFilePath) 70 | } 71 | 72 | // Delete neetCode150Problems.json if it exists 73 | if (fs.existsSync(neetCode150ProblemsFilePath)) { 74 | fs.unlinkSync(neetCode150ProblemsFilePath) 75 | } 76 | } 77 | describe("Scrape functions", () => { 78 | beforeAll(async () => { 79 | uniqueTestDirectory = path.join(testDirectory, uuidv4()) 80 | removeFiles() 81 | console.log( 82 | "Before tests, Attemping to remove test directory", 83 | testDirectory 84 | ) 85 | if (fs.existsSync(testDirectory)) { 86 | fs.rm(testDirectory) 87 | } 88 | jest.setTimeout(100000) // Setting global timeout for all tests in this file 89 | const puppeteerSetup = await initiatePuppeteer() 90 | browser = puppeteerSetup.browser 91 | page = puppeteerSetup.page 92 | }) 93 | 94 | afterEach(() => { 95 | // Define filePath for each file to be deleted 96 | removeFiles() 97 | }) 98 | afterAll(async () => { 99 | await browser.close() 100 | console.log( 101 | "After all tests, Attemping to remove test directory", 102 | testDirectory 103 | ) 104 | if (fs.existsSync(testDirectory)) { 105 | fs.rm(testDirectory, { recursive: true }) 106 | } 107 | }) 108 | 109 | it("Scrape Categories returns expected categories ", async () => { 110 | const { categories } = await scrapeCategories(page) 111 | expect(categories).toEqual(currentCategories) 112 | }, 150000) 113 | // The order has to start at all problems because when you have a category opened that's not in the other tabs, the tab closes. So you have to start with the tab that has all the categories 114 | // Might need to find a better way to scrape the categories, since this is highly dependent on the website 115 | test("Scrape All Problems returns expected problems count", async () => { 116 | const problems = await scrapeAllProblems(page) 117 | // This number will change as more problems are added to the site (might need to make a function to get the count dynamically) 118 | expect(problems.length).toBe(441) 119 | }) 120 | 121 | test("Scrape Blind 75 Problems returns expected problems count", async () => { 122 | const problems = await scrapeBlind75Problems(page) 123 | expect(problems.length).toBe(75) 124 | }) 125 | 126 | test("Scrape NeetCode 150 Problems returns expected problems count", async () => { 127 | const problems = await scrapeNeetCode150Problems(page) 128 | expect(problems.length).toBe(150) 129 | }) 130 | 131 | test("Should save all problems to JSON file", async () => { 132 | const problems = await scrapeAllProblems(page) 133 | saveProblemstoJSON("allProblems.json", testDirectory, problems) 134 | const filePath = path.join(__dirname, `${testDirectory}/allProblems.json`) 135 | expect(fs.existsSync(filePath)).toBe(true) 136 | const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) 137 | expect(content).toEqual(problems) 138 | }) 139 | 140 | test("Should save Blind 75 problems to JSON file", async () => { 141 | const problems = await scrapeBlind75Problems(page) 142 | saveProblemstoJSON("blind75Problems.json", testDirectory, problems) 143 | const filePath = path.join( 144 | __dirname, 145 | `${testDirectory}/blind75Problems.json` 146 | ) 147 | expect(fs.existsSync(filePath)).toBe(true) 148 | const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) 149 | expect(content).toEqual(problems) 150 | }) 151 | test("Should save NeetCode 150 problems to JSON file", async () => { 152 | const problems = await scrapeNeetCode150Problems(page) 153 | saveProblemstoJSON("neetCode150Problems.json", testDirectory, problems) 154 | const filePath = path.join( 155 | __dirname, 156 | `${testDirectory}/neetCode150Problems.json` 157 | ) 158 | expect(fs.existsSync(filePath)).toBe(true) 159 | const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) 160 | expect(content).toEqual(problems) 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plasmo/templates/tsconfig.base", 3 | "exclude": ["node_modules"], 4 | "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"], 5 | "compilerOptions": { 6 | "paths": { 7 | "~*": ["./*"] 8 | }, 9 | "baseUrl": "." 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | type TopicTag = { 2 | name: string 3 | id: string 4 | slug: string 5 | } 6 | export type UserState = { 7 | leetcodeProblemSolved: boolean 8 | leetCodeProblem: { 9 | name: string 10 | url: string 11 | } 12 | lastSubmissionDate: Date 13 | solvedListenerActive: boolean 14 | lastAttemptedUrl: string 15 | 16 | urlListener: ( 17 | details: chrome.webRequest.WebRequestBodyDetails 18 | ) => void | chrome.webRequest.BlockingResponse 19 | includePremium: boolean 20 | hyperTortureMode: boolean 21 | HTcurrentStreak: number 22 | } 23 | 24 | export type APILeetCodeProblem = { 25 | acRate: number 26 | difficulty: "Easy" | "Medium" | "Hard" 27 | 28 | freqBar: number 29 | 30 | frontendQuestionId: string 31 | 32 | hasSolution: boolean 33 | 34 | hasVideoSolution: boolean 35 | 36 | isFavor: boolean 37 | 38 | paidOnly: boolean 39 | 40 | status: string 41 | title: string 42 | titleSlug: string 43 | 44 | topicTags: TopicTag[] 45 | } 46 | 47 | export type JSONLeetCodeProblem = { 48 | category: string 49 | href: string 50 | text: string 51 | difficulty: "Easy" | "Medium" | "Hard" 52 | isPremium: boolean 53 | } 54 | -------------------------------------------------------------------------------- /web_scraper/run_web_scraper.js: -------------------------------------------------------------------------------- 1 | const { 2 | initiatePuppeteer, 3 | scrapeBlind75Problems, 4 | scrapeNeetCode150Problems, 5 | scrapeAllProblems, 6 | scrapeCategories, 7 | saveProblemstoJSON 8 | } = require("./web_scrape_problems") 9 | 10 | ;(async () => { 11 | const { browser, page } = await initiatePuppeteer() 12 | const saveDirectoryLocation = "./leetcode-problems" 13 | 14 | await scrapeCategories(page) 15 | // The order has to start at all problems because when you have a category opened that's not in the other tabs, the tab closes. So you have to start with the tab that has all the categories 16 | // Might need to find a better way to scrape the categories, since this is highly dependent on the website 17 | const allProblems = await scrapeAllProblems(page) 18 | const blind75Problems = await scrapeBlind75Problems(page) 19 | const neetCode150Problems = await scrapeNeetCode150Problems(page) 20 | saveProblemstoJSON("allProblems.json", saveDirectoryLocation, allProblems) 21 | saveProblemstoJSON( 22 | "blind75Problems.json", 23 | saveDirectoryLocation, 24 | blind75Problems 25 | ) 26 | saveProblemstoJSON( 27 | "neetCode150Problems.json", 28 | saveDirectoryLocation, 29 | neetCode150Problems 30 | ) 31 | await browser.close() 32 | })() 33 | -------------------------------------------------------------------------------- /web_scraper/web_scrape_problems.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require("puppeteer") 2 | const fs = require("fs") 3 | const path = require("path") 4 | 5 | const url = "https://neetcode.io/practice/" 6 | 7 | // This function will add a delay to the code execution (in milliseconds) 8 | const addDelay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) 9 | 10 | const initiatePuppeteer = async () => { 11 | const browser = await puppeteer.launch({ 12 | headless: false, 13 | defaultViewport: null 14 | }) 15 | const page = await browser.newPage() 16 | await page.goto(url) 17 | return { browser, page } 18 | } 19 | 20 | const scrapeCategories = async (page) => { 21 | // Click on the all problems tab to get all the categories 22 | // Might need to find a better way to scrape the categories, since this is highly dependent on the website 23 | const tabLinks = await page.$$( 24 | "div.tabs.is-centered.is-boxed.is-large ul.tabs-list li a.tab-link" 25 | ) 26 | // If the website changes, and the tabs are no longer in the same order, or there's a new tab, you'll need to change this index 27 | await tabLinks[2].click() 28 | // Wait for necessary elements 29 | await page.waitForSelector("app-pattern-table", { timeout: 1000 }) 30 | await page.waitForSelector( 31 | "button.flex-container-row.accordion.button.is-fullwidth.ng-tns-c41-0", 32 | { timeout: 1000 } 33 | ) 34 | 35 | // Get category count 36 | const categoryCount = await page.$$eval( 37 | "app-pattern-table", 38 | (categories) => categories.length 39 | ) 40 | 41 | // Expand each category to reveal problems 42 | for (let i = 0; i < categoryCount; i++) { 43 | const selector = `button.flex-container-row.accordion.button.is-fullwidth.ng-tns-c41-${i}` 44 | await page.waitForSelector(selector) 45 | await addDelay(750) 46 | await page.click(selector) 47 | } 48 | 49 | // Extract category names 50 | const categories = await page.$$eval( 51 | "button.flex-container-row.accordion.button.is-fullwidth.active", 52 | (buttons) => { 53 | return buttons 54 | .map((button) => { 55 | const paragraph = button.querySelector("p") 56 | return paragraph ? paragraph.textContent.trim() : null 57 | }) 58 | .filter(Boolean) 59 | } 60 | ) 61 | 62 | return { categories, categoryCount } 63 | } 64 | 65 | const scrapeProblemsFromTab = async (page, tabIndex) => { 66 | const tabLinks = await page.$$( 67 | "div.tabs.is-centered.is-boxed.is-large ul.tabs-list li a.tab-link" 68 | ) 69 | await tabLinks[tabIndex].click() 70 | await page.waitForTimeout(3000) 71 | // Extract problem details 72 | const problems = await await page.$$eval("tr.ng-star-inserted", (rows) => { 73 | return rows.map((row) => { 74 | const anchor = row.querySelector("td a.table-text") 75 | const isPremium = row.querySelector( 76 | "td a.has-tooltip-bottom.ng-star-inserted" 77 | ) 78 | const difficultyElement = row.querySelector("td.diff-col b") 79 | const container = row.closest(".accordion-container") 80 | const categoryElement = container 81 | ? container.querySelector( 82 | "button.flex-container-row.accordion.button.is-fullwidth.active p" 83 | ) 84 | : null 85 | const category = categoryElement 86 | ? categoryElement.textContent.trim() 87 | : null 88 | return { 89 | category: category, 90 | href: anchor ? anchor.href : null, 91 | text: anchor ? anchor.textContent.trim() : null, 92 | // In the future we could use the lintcode url as well, not sure 93 | difficulty: difficultyElement 94 | ? difficultyElement.textContent.trim() 95 | : null, 96 | isPremium: isPremium ? true : false 97 | } 98 | }) 99 | }) 100 | return problems 101 | } 102 | 103 | //These functions highly depend on their position in the tabs, and number 104 | //of tabs. If the website changes, these functions will need to be updated 105 | //to reflect the changes. 106 | // Might need to find a better way to scrape the problems 107 | 108 | //Adding scrapeCategories to each function to ensure that the categories are scraped 109 | const scrapeBlind75Problems = async (page) => { 110 | return await scrapeProblemsFromTab(page, 1) 111 | } 112 | 113 | const scrapeNeetCode150Problems = async (page) => { 114 | return await scrapeProblemsFromTab(page, 2) 115 | } 116 | 117 | const scrapeAllProblems = async (page) => { 118 | return await scrapeProblemsFromTab(page, 3) 119 | } 120 | 121 | const saveProblemstoJSON = (filename, dirLocation, data) => { 122 | const directory = path.join(__dirname, dirLocation) // Adjust the path based on your directory structure 123 | console.log(`Trying to save data to ${dirLocation}/${filename}`) 124 | try { 125 | if (!fs.existsSync(directory)) { 126 | console.log(`Directory ${directory} doesn't exist. Creating now.`) 127 | fs.mkdirSync(directory, { recursive: true }) // Creates the directory if it doesn't exist 128 | } 129 | } catch (err) { 130 | console.error(err) 131 | } 132 | try { 133 | fs.writeFileSync( 134 | path.join(directory, filename), 135 | JSON.stringify(data, null, 2), 136 | "utf-8" 137 | ) 138 | } catch (err) { 139 | console.error(err) 140 | } 141 | console.log(`Data saved to ${directory}`) 142 | } 143 | 144 | /* 145 | Just in case the website changes, and the tabs are no longer in the same order, or there's a new tab, here's the code to check (hopefully it still works) 146 | const tabCount = ( 147 | await page.$$( 148 | 'div.tabs.is-centered.is-boxed.is-large ul.tabs-list li a.tab-link' 149 | ) 150 | ).length; 151 | */ 152 | 153 | module.exports = { 154 | initiatePuppeteer, 155 | scrapeCategories, 156 | scrapeBlind75Problems, 157 | scrapeNeetCode150Problems, 158 | scrapeAllProblems, 159 | saveProblemstoJSON 160 | } 161 | --------------------------------------------------------------------------------