├── backend ├── public │ └── .gitkeep ├── .gitignore ├── api │ └── index.ts ├── vercel.json ├── src │ ├── index.ts │ ├── routes │ │ ├── contest │ │ │ ├── contest.route.ts │ │ │ └── contest.controller.ts │ │ └── index.ts │ ├── app.ts │ ├── types.ts │ └── platforms │ │ ├── codeforces.ts │ │ ├── codingninja.ts │ │ ├── cache.ts │ │ ├── codechef.ts │ │ ├── leetcode.ts │ │ ├── geeksforgeeks.ts │ │ └── atcoder.ts ├── package.json ├── tsconfig.json └── package-lock.json ├── browser-extension ├── src │ ├── App.css │ ├── vite-env.d.ts │ ├── types │ │ ├── theme.ts │ │ └── contest.ts │ ├── main.tsx │ ├── components │ │ ├── Credits.tsx │ │ ├── Navbar.tsx │ │ ├── FilterMenu.tsx │ │ ├── Loader.tsx │ │ ├── Alarms.tsx │ │ ├── SettingsMenu.tsx │ │ ├── ContestList.tsx │ │ └── Card.tsx │ ├── index.css │ ├── hooks │ │ ├── useTheme.ts │ │ └── useContests.ts │ ├── App.tsx │ └── assets │ │ └── react.svg ├── postcss.config.js ├── public │ ├── logo │ │ ├── icons48.png │ │ └── icons96.png │ ├── images │ │ └── platforms │ │ │ ├── atcoder.png │ │ │ ├── codechef.jpeg │ │ │ ├── leetcode.png │ │ │ ├── codeforces.png │ │ │ ├── codingninja.jpg │ │ │ └── geeksforgeeks.png │ ├── manifest.json │ ├── background.js │ └── vite.svg ├── versions │ ├── contest-list-1.0.0.zip │ ├── contest-list-1.1.0.zip │ ├── contest-notifier-1.1.1.zip │ └── contest-notifier-1.1.2.zip ├── vite.config.ts ├── tailwind.config.js ├── tsconfig.node.json ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── tsconfig.json ├── package.json └── README.md ├── LICENSE └── README.md /backend/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /browser-extension/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /browser-extension/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /backend/api/index.ts: -------------------------------------------------------------------------------- 1 | import app from "../src/app" 2 | 3 | export default app; -------------------------------------------------------------------------------- /browser-extension/src/types/theme.ts: -------------------------------------------------------------------------------- 1 | export enum MODE { 2 | light = "light", 3 | dark = "dark" 4 | } -------------------------------------------------------------------------------- /backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ 3 | "source": "/(.*)", 4 | "destination": "/api" 5 | }] 6 | } 7 | -------------------------------------------------------------------------------- /browser-extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import app from "./app"; 2 | const PORT = 3000; 3 | 4 | app.listen(PORT, () => { 5 | console.log(`Server is running.`); 6 | }) -------------------------------------------------------------------------------- /browser-extension/public/logo/icons48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/logo/icons48.png -------------------------------------------------------------------------------- /browser-extension/public/logo/icons96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/logo/icons96.png -------------------------------------------------------------------------------- /browser-extension/versions/contest-list-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/versions/contest-list-1.0.0.zip -------------------------------------------------------------------------------- /browser-extension/versions/contest-list-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/versions/contest-list-1.1.0.zip -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/atcoder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/atcoder.png -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/codechef.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/codechef.jpeg -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/leetcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/leetcode.png -------------------------------------------------------------------------------- /browser-extension/versions/contest-notifier-1.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/versions/contest-notifier-1.1.1.zip -------------------------------------------------------------------------------- /browser-extension/versions/contest-notifier-1.1.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/versions/contest-notifier-1.1.2.zip -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/codeforces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/codeforces.png -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/codingninja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/codingninja.jpg -------------------------------------------------------------------------------- /browser-extension/public/images/platforms/geeksforgeeks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VaibhavArora314/contest-notifier-extension/HEAD/browser-extension/public/images/platforms/geeksforgeeks.png -------------------------------------------------------------------------------- /browser-extension/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /browser-extension/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | darkMode: "class", 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | } 13 | 14 | -------------------------------------------------------------------------------- /backend/src/routes/contest/contest.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { UpcomingContestsController } from "./contest.controller"; 3 | 4 | const contestRouter = Router(); 5 | 6 | contestRouter.get("/upcoming", UpcomingContestsController); 7 | 8 | export default contestRouter; 9 | -------------------------------------------------------------------------------- /browser-extension/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import rootRouter from "./routes"; 3 | import cors from "cors"; 4 | import bodyParser from "body-parser"; 5 | 6 | const app = express(); 7 | app.use(cors()); 8 | app.use(bodyParser.json()); 9 | 10 | app.use('/api', rootRouter); 11 | 12 | export default app; -------------------------------------------------------------------------------- /browser-extension/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /backend/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, Router } from "express"; 2 | import contestRouter from "./contest/contest.route"; 3 | 4 | const rootRouter = Router(); 5 | 6 | rootRouter.get('/', (req:Request,res:Response) => { 7 | res.json({ 8 | message: "Hello" 9 | }) 10 | }) 11 | rootRouter.use('/contests', contestRouter); 12 | 13 | export default rootRouter; -------------------------------------------------------------------------------- /browser-extension/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /browser-extension/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backend/src/types.ts: -------------------------------------------------------------------------------- 1 | export enum PLATFORM { 2 | CODECHEF = "Codechef", 3 | CODEFORCES = "Codeforces", 4 | LEETCODE = "Leetcode", 5 | GEEKSFORGEEKS = "GeeksForGeeks", 6 | ATCODER = "AtCoder", 7 | CODINGNINJAS = "CodingNinjas" 8 | } 9 | 10 | export interface CONTEST_INTERFACE { 11 | site: PLATFORM, 12 | title: string, 13 | startTime: number, 14 | endTime: number, 15 | duration: number, 16 | url: string 17 | } -------------------------------------------------------------------------------- /browser-extension/src/types/contest.ts: -------------------------------------------------------------------------------- 1 | export enum PLATFORM { 2 | CODECHEF = "Codechef", 3 | CODEFORCES = "Codeforces", 4 | LEETCODE = "Leetcode", 5 | GEEKSFORGEEKS = "GeeksForGeeks", 6 | ATCODER = "AtCoder", 7 | CODINGNINJAS = "CodingNinjas" 8 | } 9 | export interface CONTEST_INTERFACE { 10 | site: PLATFORM, 11 | title: string, 12 | startTime: number, 13 | endTime: number, 14 | duration: number, 15 | url: string 16 | } -------------------------------------------------------------------------------- /browser-extension/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /browser-extension/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "version": "1.1.4", 4 | "name": "Contests Notifier", 5 | "description": "Contest Notifier is your go-to extension for tracking all coding contests and receiving timely notifications.", 6 | "action": { 7 | "default_popup": "index.html" 8 | }, 9 | "icons": { 10 | "48": "logo/icons48.png", 11 | "96": "logo/icons96.png" 12 | }, 13 | "permissions": ["alarms", "notifications"], 14 | "background": { 15 | "service_worker": "background.js" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browser-extension/src/components/Credits.tsx: -------------------------------------------------------------------------------- 1 | const GITHUB_URL = "https://github.com/VaibhavArora314"; 2 | 3 | const Credits = () => { 4 | return ( 5 |
6 |

7 | Credits 8 | 13 | Vaibhav Arora 14 | 15 |

16 |
17 | ); 18 | }; 19 | 20 | export default Credits; -------------------------------------------------------------------------------- /browser-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /browser-extension/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | width: 500px; 7 | height: 500px; 8 | overflow-x: hidden; 9 | } 10 | 11 | /* styles for scrollbar */ 12 | /* For WebKit browsers (Chrome, Safari) */ 13 | ::-webkit-scrollbar { 14 | width: 4px; 15 | } 16 | 17 | ::-webkit-scrollbar-thumb { 18 | background-color: #4a4a4a; 19 | border-radius: 100%; /* Rounded corners */ 20 | } 21 | 22 | ::-webkit-scrollbar-track { 23 | background-color: #f1f1f1; 24 | } 25 | 26 | /* For Firefox */ 27 | * { 28 | scrollbar-width: thin; 29 | scrollbar-color: #4a4a4a #f1f1f1; 30 | } 31 | 32 | /* For Edge and IE */ 33 | html { 34 | scrollbar-width: thin; 35 | scrollbar-color: #4a4a4a #f1f1f1; 36 | } 37 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon src/index.ts", 8 | "build": "tsc -b", 9 | "vercel-build": "echo hello", 10 | "start": "ts-node src/index.ts", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "axios": "^1.6.8", 18 | "body-parser": "^1.20.2", 19 | "cheerio": "^1.0.0-rc.12", 20 | "cors": "^2.8.5", 21 | "express": "^4.19.2", 22 | "ts-node": "^10.9.2", 23 | "typescript": "^5.4.5" 24 | }, 25 | "devDependencies": { 26 | "@types/cors": "^2.8.17", 27 | "@types/express": "^4.17.21" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /browser-extension/src/hooks/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { MODE } from "../types/theme"; 3 | 4 | const THEME_KEY = "theme"; 5 | 6 | const useTheme = () => { 7 | const [theme, setTheme] = useState(localStorage.getItem(THEME_KEY) as MODE || MODE.light); 8 | 9 | useEffect(() => { 10 | if (theme == MODE.dark) { 11 | document.documentElement.classList.add(MODE.dark); 12 | } else { 13 | document.documentElement.classList.remove(MODE.dark); 14 | } 15 | 16 | localStorage.setItem(THEME_KEY,theme); 17 | }, [theme]); 18 | 19 | const setDarkMode = (val:boolean) => { 20 | if (val) setTheme(MODE.dark); 21 | else setTheme(MODE.light); 22 | } 23 | 24 | return { theme, setDarkMode }; 25 | }; 26 | 27 | export default useTheme; -------------------------------------------------------------------------------- /browser-extension/src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import SettingsIcon from "@mui/icons-material/Settings"; 3 | import NotificationsIcon from "@mui/icons-material/Notifications"; 4 | 5 | const Navbar = () => { 6 | return ( 7 |
8 | 12 | Contests Notifier 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default Navbar; 28 | -------------------------------------------------------------------------------- /browser-extension/public/background.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const showNotification = (title, message, alarmName) => { 3 | chrome.notifications.create(alarmName, { 4 | type: "basic", 5 | iconUrl: "logo/icons96.png", 6 | title: title, 7 | message: message, 8 | priority: 1, 9 | }); 10 | }; 11 | 12 | chrome.alarms.onAlarm.addListener((alarm) => { 13 | if (alarm.name.startsWith("contest_")) { 14 | const contestName = alarm.name.replace("contest_", "") //.split("&url=")[0]; 15 | 16 | showNotification( 17 | "Upcoming Contest", 18 | `Contest ${contestName} starts in 10 minutes!`, 19 | alarm.name 20 | ); 21 | } 22 | }); 23 | 24 | // chrome.notifications.onClicked.addListener((notificationId) => { 25 | // const contestURL = notificationId.split("&url=")[1]; 26 | // if (contestURL) chrome.tabs.create({ url: contestURL }); 27 | // }); 28 | -------------------------------------------------------------------------------- /browser-extension/src/components/FilterMenu.tsx: -------------------------------------------------------------------------------- 1 | const options = ["All", "Ongoing", "In next 24hrs", "In next 7days"]; 2 | 3 | const FilterMenu = ({curFilter,updateFilter}: { 4 | curFilter: string, 5 | updateFilter: (option:string) => void 6 | }) => { 7 | return ( 8 |
9 | {options.map( 10 | (option) => ( 11 | 22 | ) 23 | )} 24 |
25 | ) 26 | } 27 | 28 | export default FilterMenu 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Vaibhav Arora 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 | -------------------------------------------------------------------------------- /browser-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-extension", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@emotion/styled": "^11.13.0", 14 | "@mui/icons-material": "^5.16.7", 15 | "axios": "^1.6.8", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-router-dom": "^6.26.0", 19 | "react-toastify": "^10.0.5" 20 | }, 21 | "devDependencies": { 22 | "@types/chrome": "^0.0.267", 23 | "@types/react": "^18.2.66", 24 | "@types/react-dom": "^18.2.22", 25 | "@typescript-eslint/eslint-plugin": "^7.2.0", 26 | "@typescript-eslint/parser": "^7.2.0", 27 | "@vitejs/plugin-react": "^4.2.1", 28 | "autoprefixer": "^10.4.19", 29 | "eslint": "^8.57.0", 30 | "eslint-plugin-react-hooks": "^4.6.0", 31 | "eslint-plugin-react-refresh": "^0.4.6", 32 | "postcss": "^8.4.38", 33 | "tailwindcss": "^3.4.3", 34 | "typescript": "^5.2.2", 35 | "vite": "^5.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /browser-extension/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /browser-extension/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /browser-extension/src/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | const Loader = () => { 2 | return ( 3 |
4 | 20 | Loading... 21 |
22 | ); 23 | }; 24 | 25 | export default Loader; 26 | -------------------------------------------------------------------------------- /backend/src/platforms/codeforces.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 3 | 4 | const CODEFORCES_BASE_URL = "https://codeforces.com/contest/"; // used for generating contest links 5 | const CODEFORCES_API = "https://codeforces.com/api/contest.list"; 6 | 7 | const fetchCodeforcesContests = async () => { 8 | try { 9 | const response = await axios.get(CODEFORCES_API); 10 | const data = response.data; 11 | 12 | return data.result || []; 13 | } catch (error) { 14 | return []; 15 | } 16 | }; 17 | 18 | const parseCodeforcesContests = (data: [any]) => { 19 | const contests: CONTEST_INTERFACE[] = []; 20 | 21 | data.forEach((element) => { 22 | if (element.phase == "BEFORE" || element.phase == "CODING") { 23 | const contest_name = element?.name || "Codeforces contest"; 24 | const url = CODEFORCES_BASE_URL + element?.id; 25 | 26 | const startMs = element?.startTimeSeconds * 1000; 27 | const duration = element?.durationSeconds / 60 || 120; // minutes 28 | const endMs = startMs + duration * 60 * 1000; 29 | 30 | const contest: CONTEST_INTERFACE = { 31 | site: PLATFORM.CODEFORCES, 32 | title: contest_name, 33 | startTime: startMs, 34 | endTime: endMs, 35 | duration, 36 | url, 37 | }; 38 | 39 | contests.push(contest); 40 | } 41 | }); 42 | 43 | return contests; 44 | }; 45 | 46 | const getCodeforcesContests = async () => { 47 | const data = await fetchCodeforcesContests(); 48 | const parsedData = parseCodeforcesContests(data); 49 | 50 | console.log("Fetched data from codeforces!", parsedData.length); 51 | 52 | return parsedData; 53 | }; 54 | 55 | export default getCodeforcesContests; 56 | -------------------------------------------------------------------------------- /backend/src/platforms/codingninja.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 3 | 4 | const CODING_NINJA_BASE_URL = "https://www.naukri.com/code360/contests/"; // used for generating contest links 5 | const CODING_NINJA_API = 6 | "https://api.codingninjas.com/api/v4/public_section/contest_list"; 7 | 8 | const fetchCodingNinjaContests = async () => { 9 | try { 10 | const {data} = await axios.get(CODING_NINJA_API); 11 | 12 | if (data?.data?.events) return data.data.events; 13 | return []; 14 | } catch (error) { 15 | console.log("Error", error); 16 | return []; 17 | } 18 | }; 19 | 20 | const parseCodingNinjaContests = (data: [any]) => { 21 | const contests: CONTEST_INTERFACE[] = []; 22 | 23 | data.forEach((element) => { 24 | const contest_name = element?.name || "Coding Ninjas contest"; 25 | const url = CODING_NINJA_BASE_URL + element?.slug; 26 | 27 | const startMs = new Date(element?.event_start_time*1000).getTime(); 28 | const endMs = new Date(element?.event_end_time*1000).getTime(); 29 | const duration = (endMs - startMs)/(1000*60) || 120; // minutes 30 | 31 | const contest: CONTEST_INTERFACE = { 32 | site: PLATFORM.CODINGNINJAS, 33 | title: contest_name, 34 | startTime: startMs, 35 | endTime: endMs, 36 | duration, 37 | url, 38 | }; 39 | 40 | contests.push(contest); 41 | }); 42 | 43 | return contests; 44 | }; 45 | 46 | const getCodingNinjaContests = async () => { 47 | const data = await fetchCodingNinjaContests(); 48 | const parsedData = parseCodingNinjaContests(data); 49 | 50 | console.log("Fetched data from CodingNinja!", parsedData.length); 51 | return parsedData; 52 | }; 53 | 54 | export default getCodingNinjaContests; 55 | -------------------------------------------------------------------------------- /backend/src/platforms/cache.ts: -------------------------------------------------------------------------------- 1 | import { CONTEST_INTERFACE } from "../types"; 2 | import { getAtcoderContests } from "./atcoder"; 3 | import getCodechefContests from "./codechef"; 4 | import getCodeforcesContests from "./codeforces"; 5 | import getCodingNinjaContests from "./codingninja"; 6 | import getGfgContests from "./geeksforgeeks"; 7 | import getLeetcodeContests from "./leetcode"; 8 | 9 | const REFRESH_TIMER = 6 * 60 * 60 * 1000; 10 | // const REFRESH_TIMER = 0; 11 | 12 | let lastRefreshTime = new Date(1970, 0, 1).getTime(); 13 | 14 | let codeforcesContests: CONTEST_INTERFACE[] = [], 15 | leetcodeContests: CONTEST_INTERFACE[] = [], 16 | codechefContests: CONTEST_INTERFACE[] = [], 17 | gfgContests: CONTEST_INTERFACE[] = [], 18 | atcoderContests: CONTEST_INTERFACE[] = [], 19 | codingninjaContests: CONTEST_INTERFACE[] = []; 20 | 21 | const refreshCache = async () => { 22 | const currentTime = new Date().getTime(); 23 | if (currentTime - lastRefreshTime <= REFRESH_TIMER) return; 24 | 25 | lastRefreshTime = currentTime; 26 | console.log("Refreshing cache"); 27 | 28 | try { 29 | [ 30 | codechefContests, 31 | codeforcesContests, 32 | leetcodeContests, 33 | gfgContests, 34 | atcoderContests, 35 | codingninjaContests, 36 | ] = await Promise.all([ 37 | getCodechefContests(), 38 | getCodeforcesContests(), 39 | getLeetcodeContests(), 40 | getGfgContests(), 41 | getAtcoderContests(), 42 | getCodingNinjaContests(), 43 | ]); 44 | 45 | console.log("Refreshed cache"); 46 | } catch (error) { 47 | console.error("Error refreshing cache:", error); 48 | } 49 | }; 50 | 51 | export { 52 | codechefContests, 53 | codeforcesContests, 54 | leetcodeContests, 55 | gfgContests, 56 | atcoderContests, 57 | codingninjaContests, 58 | refreshCache, 59 | }; 60 | -------------------------------------------------------------------------------- /backend/src/platforms/codechef.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 3 | 4 | const CODECHEF_BASE_URL = "https://www.codechef.com/"; // used for generating contest links 5 | const CODECHEF_API = 6 | "https://www.codechef.com/api/list/contests/all?sort_by=START&sorting_order=asc&offset=0&mode=all"; 7 | 8 | const fetchCodechefContests = async () => { 9 | try { 10 | const response = await axios.get(CODECHEF_API); 11 | const data = response.data; 12 | 13 | let result = []; 14 | if (data?.present_contests) result = data?.present_contests; 15 | if (data?.future_contests) result = [...result, ...data.future_contests]; 16 | return result; 17 | } catch (error) { 18 | return []; 19 | } 20 | }; 21 | 22 | const parseCodechefContests = (data: [any]) => { 23 | const contests: CONTEST_INTERFACE[] = []; 24 | 25 | data.forEach((element) => { 26 | const contest_name = element?.contest_name || "Codechef contest"; 27 | const url = CODECHEF_BASE_URL + element?.contest_code; 28 | 29 | const startMs = new Date(element?.contest_start_date_iso).getTime(); 30 | const endMs = new Date(element?.contest_end_date_iso).getTime(); 31 | const duration = element?.duration || 120; // minutes 32 | 33 | const contest: CONTEST_INTERFACE = { 34 | site: PLATFORM.CODECHEF, 35 | title: contest_name, 36 | startTime: startMs, 37 | endTime: endMs, 38 | duration, 39 | url, 40 | }; 41 | 42 | contests.push(contest); 43 | }); 44 | 45 | return contests; 46 | }; 47 | 48 | const getCodechefContests = async () => { 49 | const data = await fetchCodechefContests(); 50 | const parsedData = parseCodechefContests(data); 51 | 52 | console.log("Fetched data from codechef!",parsedData.length); 53 | 54 | return parsedData; 55 | }; 56 | 57 | export default getCodechefContests; -------------------------------------------------------------------------------- /backend/src/platforms/leetcode.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 3 | 4 | const LEETCODE_BASE_URL = "https://leetcode.com/contest/"; // used for generating contest links 5 | const LEETCODE_API = "https://leetcode.com/graphql"; 6 | 7 | const fetchLeetcodeContests = async () => { 8 | try { 9 | const response = await axios.post(LEETCODE_API, { 10 | headers: { 11 | "Content-Type": "application/json", 12 | }, 13 | query: `{ 14 | topTwoContests{ 15 | title 16 | startTime 17 | duration 18 | cardImg 19 | titleSlug 20 | } 21 | }`, 22 | }); 23 | const data = response.data; 24 | 25 | return data.data.topTwoContests || []; 26 | } catch (error) { 27 | return []; 28 | } 29 | }; 30 | 31 | const parseLeetcodeContests = (data: [any]) => { 32 | const contests: CONTEST_INTERFACE[] = []; 33 | 34 | data.forEach((element) => { 35 | const contest_name = element?.title || "Leetcode contest"; 36 | const url = LEETCODE_BASE_URL + element?.titleSlug; 37 | 38 | const startMs = element?.startTime*1000; 39 | const duration = element?.duration/60 || 90; // minutes 40 | const endMs = startMs + duration*60*1000; 41 | 42 | const contest: CONTEST_INTERFACE = { 43 | site: PLATFORM.LEETCODE, 44 | title: contest_name, 45 | startTime: startMs, 46 | endTime: endMs, 47 | duration, 48 | url, 49 | }; 50 | 51 | contests.push(contest); 52 | }); 53 | 54 | return contests; 55 | }; 56 | 57 | const getLeetcodeContests = async () => { 58 | const data = await fetchLeetcodeContests(); 59 | const parsedData = parseLeetcodeContests(data); 60 | 61 | console.log("Fetched data from leetcode!",parsedData.length); 62 | 63 | return parsedData; 64 | }; 65 | 66 | 67 | export default getLeetcodeContests; 68 | -------------------------------------------------------------------------------- /browser-extension/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import axios from "axios"; 3 | import ContestList from "./components/ContestList"; 4 | import SettingsMenu from "./components/SettingsMenu"; 5 | import useTheme from "./hooks/useTheme"; 6 | import Credits from "./components/Credits"; 7 | import AlarmList from "./components/Alarms"; 8 | import useContests from "./hooks/useContests"; 9 | import { Route, Routes } from "react-router-dom"; 10 | import Navbar from "./components/Navbar"; 11 | import { ToastContainer } from "react-toastify"; 12 | import "react-toastify/dist/ReactToastify.css"; 13 | 14 | // axios.defaults.baseURL = "http://localhost:3000/api"; 15 | axios.defaults.baseURL = "https://cp-list.vercel.app/api"; 16 | 17 | function App() { 18 | const { loading, error, contests, platforms, setPlatforms } = useContests(); 19 | const { theme, setDarkMode } = useTheme(); 20 | 21 | return ( 22 | <> 23 |
24 |
25 | 26 | 27 | 36 | } 37 | /> 38 | } /> 39 | 47 | } 48 | /> 49 | 50 |
51 | 52 |
53 | 54 | 55 | ); 56 | } 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /backend/src/platforms/geeksforgeeks.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 3 | 4 | const GFG_BASE_URL = "https://practice.geeksforgeeks.org/contest/"; // used for generating contest links 5 | const GFG_API = 6 | "https://practiceapi.geeksforgeeks.org/api/vr/events/?page_number=1&sub_type=all&type=contest"; 7 | 8 | const fetchGfgContests = async () => { 9 | try { 10 | const response = await axios.get(GFG_API); 11 | const data = response.data; 12 | 13 | let result = []; 14 | if (data?.results?.upcoming) result = data?.results?.upcoming; 15 | return result; 16 | } catch (error) { 17 | return []; 18 | } 19 | }; 20 | 21 | const parseGfgContests = (data: [any]) => { 22 | const contests: CONTEST_INTERFACE[] = []; 23 | 24 | data.forEach((element) => { 25 | const contest_name = element?.name || "Geeks for Geeks contest"; 26 | const url = GFG_BASE_URL + element?.slug; 27 | 28 | const startDate:Date = new Date(new Date(element?.start_time).getTime() - (5.5 * 60 * 60 * 1000)); 29 | const startMs = startDate.getTime(); 30 | const endDate:Date = new Date(new Date(element?.end_time).getTime() - (5.5 * 60 * 60 * 1000)); 31 | const endMs = endDate.getTime(); 32 | const duration:number = Math.abs(endDate.getTime() - startDate.getTime())/(1000*60) || 120; // minutes 33 | 34 | const contest: CONTEST_INTERFACE = { 35 | site: PLATFORM.GEEKSFORGEEKS, 36 | title: contest_name, 37 | startTime: startMs, 38 | endTime: endMs, 39 | duration, 40 | url, 41 | }; 42 | 43 | contests.push(contest); 44 | }); 45 | 46 | return contests; 47 | }; 48 | 49 | const getGfgContests = async () => { 50 | const data = await fetchGfgContests(); 51 | const parsedData = parseGfgContests(data); 52 | 53 | console.log("Fetched data from gfg!",parsedData.length); 54 | 55 | return parsedData; 56 | }; 57 | 58 | export default getGfgContests; -------------------------------------------------------------------------------- /browser-extension/src/hooks/useContests.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import { CONTEST_INTERFACE, PLATFORM } from "../types/contest"; 4 | 5 | const FetchInteravlMs = 60 * 60 * 1000; // 1 hour 6 | const PLATFORM_KEY = "platform"; 7 | const CONTESTS_KEY = "contests"; 8 | 9 | export const DEFAULT_PLATFORMS = [ 10 | PLATFORM.CODECHEF, 11 | PLATFORM.CODEFORCES, 12 | PLATFORM.LEETCODE, 13 | PLATFORM.ATCODER, 14 | PLATFORM.GEEKSFORGEEKS, 15 | PLATFORM.CODINGNINJAS, 16 | ]; 17 | 18 | const useContests = () => { 19 | const [loading, setLoading] = useState(true); 20 | const [error, setError] = useState(false); 21 | const [contests, setContests] = useState( 22 | JSON.parse(localStorage.getItem(CONTESTS_KEY) as string) || [] 23 | ); 24 | const [platforms, setPlatforms] = useState( 25 | JSON.parse(localStorage.getItem(PLATFORM_KEY) as string) || 26 | DEFAULT_PLATFORMS 27 | ); 28 | 29 | useEffect(() => { 30 | const fetchContests = async () => { 31 | try { 32 | const response = await axios.get( 33 | `/contests/upcoming?platforms=${JSON.stringify(platforms)}` 34 | ); 35 | const data = response.data; 36 | 37 | setContests(data?.upcoming_contests); 38 | localStorage.setItem(CONTESTS_KEY, JSON.stringify(data?.upcoming_contests)); 39 | setError(false); 40 | } catch (error) { 41 | // setContests([]); 42 | setError(true); 43 | } 44 | setLoading(false); 45 | }; 46 | 47 | fetchContests(); 48 | const interval = setInterval(fetchContests, FetchInteravlMs); 49 | 50 | localStorage.setItem(PLATFORM_KEY, JSON.stringify([...platforms])); 51 | 52 | return () => { 53 | clearInterval(interval); 54 | }; 55 | }, [platforms]); 56 | 57 | // console.log(error,contests); 58 | 59 | return { loading, error, contests, platforms, setPlatforms }; 60 | }; 61 | 62 | export default useContests; 63 | -------------------------------------------------------------------------------- /backend/src/routes/contest/contest.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../../types"; 3 | import { atcoderContests, codechefContests, codeforcesContests, codingninjaContests, gfgContests, leetcodeContests, refreshCache } from "../../platforms/cache"; 4 | 5 | const defaultArray = Object.values(PLATFORM); 6 | const DURATION_LIMIT = 15*24*60; 7 | 8 | export const UpcomingContestsController = async ( 9 | req: Request, 10 | res: Response 11 | ) => { 12 | let platforms: string[]; 13 | const includedPlatorms: string[] = []; 14 | try { 15 | platforms = JSON.parse(req.query.platforms as string); 16 | if (!platforms || !Array.isArray(platforms)) 17 | throw new Error(); 18 | } catch (error) { 19 | platforms = defaultArray; 20 | } 21 | 22 | let contests:CONTEST_INTERFACE[] = []; 23 | 24 | await refreshCache(); 25 | 26 | if (platforms.includes(PLATFORM.CODECHEF)){ 27 | contests = [...contests,...codechefContests]; 28 | includedPlatorms.push(PLATFORM.CODECHEF); 29 | } 30 | if (platforms.includes(PLATFORM.LEETCODE)){ 31 | contests = [...contests,...leetcodeContests]; 32 | includedPlatorms.push(PLATFORM.LEETCODE); 33 | } 34 | if (platforms.includes(PLATFORM.CODEFORCES)){ 35 | contests = [...contests,...codeforcesContests]; 36 | includedPlatorms.push(PLATFORM.CODEFORCES); 37 | } 38 | if (platforms.includes(PLATFORM.GEEKSFORGEEKS)){ 39 | contests = [...contests,...gfgContests]; 40 | includedPlatorms.push(PLATFORM.GEEKSFORGEEKS); 41 | } 42 | if (platforms.includes(PLATFORM.ATCODER)){ 43 | contests = [...contests,...atcoderContests]; 44 | includedPlatorms.push(PLATFORM.ATCODER); 45 | } 46 | if (platforms.includes(PLATFORM.CODINGNINJAS)){ 47 | contests = [...contests,...codingninjaContests]; 48 | includedPlatorms.push(PLATFORM.CODINGNINJAS); 49 | } 50 | 51 | contests = contests.filter((contest) => (contest.duration <= DURATION_LIMIT)); 52 | 53 | contests.sort( 54 | (contest1, contest2) => 55 | (contest1.startTime as number) - (contest2.startTime as number) 56 | ); 57 | 58 | res.status(200).json({ 59 | platforms: includedPlatorms, 60 | upcoming_contests: contests, 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /browser-extension/src/components/Alarms.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const AlarmList = () => { 4 | const [alarms, setAlarms] = useState([]); 5 | 6 | useEffect(() => { 7 | fetchAlarms(); 8 | }, []); 9 | 10 | const fetchAlarms = () => { 11 | chrome.alarms.getAll((a) => { 12 | setAlarms(a); 13 | }); 14 | }; 15 | 16 | const removeAlarm = (alarmName: string) => { 17 | chrome.alarms.clear(alarmName, () => { 18 | fetchAlarms(); 19 | }); 20 | }; 21 | 22 | const removeAllAlarms = () => { 23 | chrome.alarms.clearAll(() => { 24 | fetchAlarms(); 25 | }); 26 | }; 27 | 28 | return ( 29 |
30 |

31 | Alarms 32 |

33 | {alarms.length > 0 ? ( 34 | <> 35 |
    36 | {alarms.map((alarm) => { 37 | const contestName = alarm.name.replace("contest_", ""); 38 | 39 | return ( 40 |
  • 44 |
    45 | 46 | Contest: {contestName} 47 | 48 |
    49 | 55 |
  • 56 | ); 57 | })} 58 |
59 | 65 | 66 | ) : ( 67 |

68 | No alarms set. To set an alarm, click on the alarm icon next to the contest name. 69 |

70 | )} 71 |
72 | ); 73 | }; 74 | 75 | export default AlarmList; 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 **Contest Notifier Browser Extension** 2 | Introducing **Contest Notifier** - Your all-in-one solution to enhance your coding contest experience! This web extension centralizes and customizes your contest tracking, with smart reminders and time-based filtering to keep you on top of your game. 🎯 3 | 4 | 🔗 **Install it from the web store [here](https://chromewebstore.google.com/detail/contests-notifier/bdlfpnghogldhcpgiknambfhdmddjghi).** 5 | 6 | ![2](https://github.com/user-attachments/assets/d83a18de-9b72-4cab-b258-7288edc4b350) 7 | 8 | ## 🛠 **Tech Stack:** 9 | - **TypeScript** 10 | - **React.js** 11 | - **Node.js** 12 | 13 | ## ✨ **Features** 14 | 1. 📅 **Centralized Contest Tracking**: View upcoming and ongoing contests from platforms like LeetCode, CodeChef, Codeforces, AtCoder, GeeksforGeeks, and CodingNinjas all in one place. 15 | 2. 🎯 **Customizable Platform & Time Selection**: Filter contests by your preferred platforms and timeframes, such as ongoing or starting within the next 24 hours. 16 | 3. ⏰ **Smart Reminder System**: Receive a reminder 10 minutes before a contest begins when you’ve set an alarm. (Make sure your device's Do Not Disturb is off to receive notifications) 17 | 18 | 🔒 **Privacy First:** We don’t store any of your data. Contest Notifier simply fetches and displays contest information. 19 | 20 | ## 📥 **Installation** 21 | ### Chrome 22 | - 📦 Download the [contest-list.zip file](https://github.com/VaibhavArora314/contest-notifier-extension/tree/main/browser-extension/versions) from the GitHub repository. 23 | - 📂 Unzip the file. 24 | - 🌐 Open Chrome and go to `chrome://extensions/`. 25 | - ⚙️ Enable "Developer mode" in the top right corner. 26 | - 📤 Click on "Load unpacked" and select the unzipped contest-list folder. 27 | 28 | ### Firefox 29 | - 📦 Download the [contest-list.zip file](https://github.com/VaibhavArora314/contest-notifier-extension/tree/main/browser-extension/versions) from the GitHub repository. 30 | - 📂 Unzip the file. 31 | - 🦊 Open Firefox and go to `about:debugging#/runtime/this-firefox`. 32 | - 📥 Click on "Load Temporary Add-on" and select any file from the unzipped contest-list folder. 33 | 34 | ## 📊 **Usage** 35 | 1. 🖱️ Click on the Contest List icon in your browser toolbar to open the extension. 36 | 2. 📜 Browse through the list of upcoming contests. 37 | 3. 🔍 Click on a contest to view detailed information. 38 | 39 | ## 🤝 **Contributing** 40 | Contributions are Welcome! You can contribute in the following ways: 41 | - 🛠 **Create an Issue** - Propose a new feature or report a bug. 42 | - 🔧 **Pull Request** - Fix a bug, typo, or refactor the code. 43 | 44 | ## 📄 **License** 45 | This project is licensed under the MIT License. See the [LICENSE file](https://github.com/VaibhavArora314/contest-notifier-extension/blob/main/LICENSE) for details. 46 | -------------------------------------------------------------------------------- /backend/src/platforms/atcoder.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import * as cheerio from "cheerio"; 3 | import { CONTEST_INTERFACE, PLATFORM } from "../types"; 4 | 5 | const ATCODER_BASE_URL = "https://atcoder.jp"; // for generating contest url 6 | const ATCODER_CONTESTS_PAGE = "https://atcoder.jp/contests/"; 7 | 8 | const parseTable = ($:cheerio.CheerioAPI,tbody:cheerio.Cheerio) => { 9 | const contests: CONTEST_INTERFACE[] = []; 10 | 11 | tbody.find('tr').each((index, element) => { 12 | const trElement = $(element); 13 | 14 | const startTimeHref = trElement.find('td').eq(0).find('a').attr('href'); 15 | const startTimeIso = startTimeHref ? startTimeHref.split('=')[1].split('&')[0] : ''; 16 | const formattedStartTimeIso = `${startTimeIso.substring(0, 4)}-${startTimeIso.substring(4, 6)}-${startTimeIso.substring(6, 8)}T${startTimeIso.substring(9, 11)}:${startTimeIso.substring(11)}`; 17 | 18 | const contestLink = trElement.find('td').eq(1).find('a').attr('href'); 19 | const contestName = trElement.find('td').eq(1).text().replace('Ⓐ', '').replace('◉', '').replace('Ⓗ', '').trim(); 20 | const duration = trElement.find('td').eq(2).text().trim(); 21 | 22 | const [hours, minutes] = duration.split(':'); 23 | const durationMinutes = Number(hours) * 60 + Number(minutes); 24 | 25 | const startTimeJST = new Date(formattedStartTimeIso); 26 | const startTime = new Date(startTimeJST.getTime() - (9 * 60 * 60 * 1000)).getTime(); // To UTC 27 | 28 | const endTime = startTime + durationMinutes * 60 * 1000; 29 | 30 | contests.push({ 31 | site: PLATFORM.ATCODER, 32 | title: contestName, 33 | startTime, 34 | endTime, 35 | duration: durationMinutes, 36 | url: ATCODER_BASE_URL + (contestLink ? contestLink : "") 37 | }); 38 | }); 39 | 40 | return contests; 41 | } 42 | 43 | export const getAtcoderContests = async () => { 44 | try { 45 | let contests: CONTEST_INTERFACE[] = []; 46 | 47 | const { data: markup } = await axios.get(ATCODER_CONTESTS_PAGE); 48 | const $ = cheerio.load(markup); 49 | 50 | const tBodyActive = $('#contest-table-action').find('tbody');; 51 | contests = [...contests, ...parseTable($,tBodyActive)]; 52 | 53 | const tbodyUpcoming = $('#contest-table-upcoming').find('tbody'); 54 | contests = [...contests, ...parseTable($,tbodyUpcoming)]; 55 | 56 | console.log('Fetched data from atcoder!', contests.length); 57 | 58 | return contests; 59 | } catch (error) { 60 | console.error(`Error fetching contests: ${error}`); 61 | return []; 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /browser-extension/src/components/SettingsMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DEFAULT_PLATFORMS } from "../hooks/useContests"; 3 | import { MODE } from "../types/theme"; 4 | 5 | type Props = { 6 | theme: MODE; 7 | platforms: string[]; 8 | setPlatforms: React.Dispatch>; 9 | setDarkMode: (val: boolean) => void; 10 | }; 11 | 12 | const SettingsMenu = ({ 13 | theme, 14 | setDarkMode, 15 | platforms, 16 | setPlatforms, 17 | }: Props) => { 18 | const handleChange = (event: React.ChangeEvent) => { 19 | const isDarkMode = event.target.value === "dark"; 20 | setDarkMode(isDarkMode); 21 | }; 22 | 23 | const handlePlatformChange = (event: React.ChangeEvent) => { 24 | const platform = event.target.value; 25 | if (platforms.includes(platform)) { 26 | setPlatforms((p) => p.filter((p) => p !== platform)); 27 | } else { 28 | setPlatforms((p) => [...p, platform]); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 |
35 |

36 | Settings 37 |

38 |
39 | 45 | 54 |
55 |
56 | 59 |
60 | {DEFAULT_PLATFORMS.map((p, index) => ( 61 | 74 | ))} 75 |
76 |
77 |
78 |
79 | ); 80 | }; 81 | 82 | export default SettingsMenu; 83 | -------------------------------------------------------------------------------- /browser-extension/src/components/ContestList.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { CONTEST_INTERFACE } from "../types/contest"; 3 | import Card from "./Card"; 4 | import Loader from "./Loader"; 5 | import FilterMenu from "./FilterMenu"; 6 | 7 | type Props = { 8 | loading: boolean; 9 | error: boolean; 10 | contests: CONTEST_INTERFACE[]; 11 | }; 12 | 13 | const ContestList = ({ loading, error, contests }: Props) => { 14 | const [filter, setFilter] = useState("All"); 15 | 16 | const filterContests = (contests: CONTEST_INTERFACE[]) => { 17 | const now = new Date(); 18 | const next24hrs = new Date(now.getTime() + 24 * 60 * 60 * 1000); 19 | const next7days = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); 20 | switch (filter) { 21 | case "Ongoing": 22 | return contests.filter( 23 | (contest) => 24 | new Date(contest.startTime) <= now && 25 | new Date(contest.endTime) >= now 26 | ); 27 | case "In next 24hrs": 28 | return contests.filter( 29 | (contest) => 30 | new Date(contest.startTime) <= next24hrs && 31 | new Date(contest.startTime) >= now 32 | ); 33 | case "In next 7days": 34 | return contests.filter( 35 | (contest) => 36 | new Date(contest.startTime) <= next7days && 37 | new Date(contest.startTime) >= now 38 | ); 39 | default: 40 | return contests; 41 | } 42 | }; 43 | 44 | const filteredContests = filterContests(contests); 45 | 46 | if (loading && contests.length === 0) 47 | return ( 48 |
49 | 50 |
51 | ); 52 | 53 | if (error && contests.length === 0) 54 | return ( 55 |
56 |

57 | An unexpected error occurred 58 |

59 |
60 | ); 61 | 62 | return ( 63 |
64 |
65 | {loading && ( 66 | 67 |

Refreshing Contests

68 |
69 | )} 70 | {error && ( 71 | 72 |

Unable to refresh contests!

73 |

Please check your internet connection.

74 |
75 | )} 76 | { 77 | setFilter(option); 78 | }} /> 79 |
80 | {filteredContests.length > 0 ? ( 81 | <> 82 |

83 | {filteredContests.length} such contest{filteredContests.length > 1 && "s"} 84 |

85 | {filteredContests.map((contest) => ( 86 | 87 | ))} 88 | 89 | ) : ( 90 |

91 | Oops! Looks like no contests! 92 |

93 | )} 94 |
95 | ); 96 | }; 97 | 98 | export default ContestList; 99 | -------------------------------------------------------------------------------- /browser-extension/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /browser-extension/src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { CONTEST_INTERFACE, PLATFORM } from "../types/contest"; 3 | import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; 4 | import AccessAlarmIcon from "@mui/icons-material/AccessAlarm"; 5 | import AlarmOffIcon from "@mui/icons-material/AlarmOff"; 6 | import { toast } from "react-toastify"; 7 | 8 | type Props = { 9 | contest: CONTEST_INTERFACE; 10 | }; 11 | 12 | enum STATUS { 13 | ongoing = "Ongoing", 14 | ended = "Ended", 15 | yetToStart = "Yet To Start", 16 | } 17 | 18 | const Card = ({ contest }: Props) => { 19 | const startDate = new Date(contest.startTime), 20 | endDate = new Date(contest.endTime); 21 | let logoUrl = "images/platforms/"; 22 | if (contest.site == PLATFORM.CODECHEF) logoUrl += "codechef.jpeg"; 23 | else if (contest.site == PLATFORM.LEETCODE) logoUrl += "leetcode.png"; 24 | else if (contest.site == PLATFORM.CODEFORCES) logoUrl += "codeforces.png"; 25 | else if (contest.site == PLATFORM.ATCODER) logoUrl += "atcoder.png"; 26 | else if (contest.site == PLATFORM.GEEKSFORGEEKS) 27 | logoUrl += "geeksforgeeks.png"; 28 | else if (contest.site == PLATFORM.CODINGNINJAS) logoUrl += "codingninja.jpg"; 29 | 30 | const [curTime, setCurTime] = useState(new Date()); 31 | const [alarmSet, setAlarmSet] = useState(false); 32 | 33 | let currentStatus: STATUS = STATUS.yetToStart; 34 | if (curTime > endDate) currentStatus = STATUS.ended; 35 | else if (curTime >= startDate && curTime <= endDate) 36 | currentStatus = STATUS.ongoing; 37 | 38 | useEffect(() => { 39 | const interval = setInterval(() => { 40 | setCurTime(new Date()); 41 | }, 1000); 42 | 43 | chrome.alarms.get("contest_" + contest.title, (alarm) => { 44 | if (alarm) setAlarmSet(true); 45 | }); 46 | 47 | return () => { 48 | clearInterval(interval); 49 | }; 50 | }, [contest.title]); 51 | 52 | const handleToggleAlarm = () => { 53 | const alarmTime = new Date( 54 | new Date(contest.startTime).getTime() - 10 * 60 * 1000 55 | ); // 10 minutes before local start time 56 | 57 | if (alarmSet) { 58 | chrome.alarms.clear("contest_" + contest.title, () => { 59 | toast("Alarm removed for the contest!"); 60 | setAlarmSet(false); 61 | }); 62 | } else { 63 | chrome.alarms.create("contest_" + contest.title, { 64 | when: alarmTime.getTime(), 65 | }); 66 | toast("Alarm set for 10 minutes before the contest!"); 67 | setAlarmSet(true); 68 | } 69 | }; 70 | 71 | return ( 72 | 77 | {contest.site} 82 |
83 |
84 | {`${contest.site} - ${contest.title}`} 85 |
86 | {currentStatus === STATUS.ended && ( 87 |
88 | 89 |

90 | {STATUS.ended} 91 |

92 |
93 | )} 94 | {currentStatus === STATUS.ongoing && ( 95 |
96 | 97 |

98 | {STATUS.ongoing} 99 |

100 |
101 | )} 102 | {currentStatus === STATUS.yetToStart && ( 103 |
104 | 105 | 106 |

107 | {getRemainingTime(contest.startTime, curTime.getTime())} 108 |

109 |
110 | { 112 | e.preventDefault(); 113 | handleToggleAlarm(); 114 | }} 115 | className="ml-1 px-2" 116 | > 117 | {alarmSet ? ( 118 | 119 | ) : ( 120 | 121 | )} 122 | 123 |
124 | )} 125 |

126 | {`Starts at ${startDate.toLocaleDateString(undefined, { 127 | year: "numeric", 128 | month: "long", 129 | day: "numeric", 130 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 131 | })}, ${startDate.toLocaleTimeString(undefined, { 132 | hour: "2-digit", 133 | minute: "2-digit", 134 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 135 | })}`} 136 |

137 |

138 | {`Ends at ${endDate.toLocaleDateString(undefined, { 139 | year: "numeric", 140 | month: "long", 141 | day: "numeric", 142 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 143 | })}, ${endDate.toLocaleTimeString(undefined, { 144 | hour: "2-digit", 145 | minute: "2-digit", 146 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 147 | })}`} 148 |

149 |

150 | {`Duration (in mins): ${contest.duration}`} 151 |

152 |
153 |
154 | ); 155 | }; 156 | 157 | const getRemainingTime = (startTime: number, curTime: number) => { 158 | const remainingTimeSec = (startTime - curTime) / 1000; 159 | const days = Math.floor(remainingTimeSec / (60 * 60 * 24)); 160 | const hours = Math.floor((remainingTimeSec / (60 * 60)) % 24); 161 | const mins = Math.floor((remainingTimeSec / 60) % 60); 162 | const sec = Math.floor(remainingTimeSec % 60); 163 | 164 | let r: string = "Starts in "; 165 | if (days >= 1) 166 | r += `${days} ${days == 1 ? "day" : "days"}, ${hours} ${ 167 | hours == 1 ? "hr" : "hrs" 168 | }`; 169 | else if (hours >= 1) 170 | r += `${hours} ${hours == 1 ? "hr" : "hrs"}, ${mins} ${ 171 | mins == 1 ? "min" : "mins" 172 | }`; 173 | else 174 | r += `${mins} ${mins == 1 ? "min" : "mins"}, ${sec} ${ 175 | sec == 1 ? "sec" : "secs" 176 | }`; 177 | 178 | return r; 179 | }; 180 | 181 | export default Card; 182 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | "exclude": [ 110 | "node_modules", 111 | "api" 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "backend", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^1.6.8", 13 | "body-parser": "^1.20.2", 14 | "cheerio": "^1.0.0-rc.12", 15 | "cors": "^2.8.5", 16 | "express": "^4.19.2", 17 | "ts-node": "^10.9.2", 18 | "typescript": "^5.4.5" 19 | }, 20 | "devDependencies": { 21 | "@types/cors": "^2.8.17", 22 | "@types/express": "^4.17.21" 23 | } 24 | }, 25 | "node_modules/@cspotcode/source-map-support": { 26 | "version": "0.8.1", 27 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 28 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 29 | "dependencies": { 30 | "@jridgewell/trace-mapping": "0.3.9" 31 | }, 32 | "engines": { 33 | "node": ">=12" 34 | } 35 | }, 36 | "node_modules/@jridgewell/resolve-uri": { 37 | "version": "3.1.2", 38 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 39 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/sourcemap-codec": { 45 | "version": "1.4.15", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 47 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 48 | }, 49 | "node_modules/@jridgewell/trace-mapping": { 50 | "version": "0.3.9", 51 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 52 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 53 | "dependencies": { 54 | "@jridgewell/resolve-uri": "^3.0.3", 55 | "@jridgewell/sourcemap-codec": "^1.4.10" 56 | } 57 | }, 58 | "node_modules/@tsconfig/node10": { 59 | "version": "1.0.11", 60 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", 61 | "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==" 62 | }, 63 | "node_modules/@tsconfig/node12": { 64 | "version": "1.0.11", 65 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 66 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 67 | }, 68 | "node_modules/@tsconfig/node14": { 69 | "version": "1.0.3", 70 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 71 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 72 | }, 73 | "node_modules/@tsconfig/node16": { 74 | "version": "1.0.4", 75 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 76 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" 77 | }, 78 | "node_modules/@types/body-parser": { 79 | "version": "1.19.5", 80 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 81 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 82 | "dev": true, 83 | "dependencies": { 84 | "@types/connect": "*", 85 | "@types/node": "*" 86 | } 87 | }, 88 | "node_modules/@types/connect": { 89 | "version": "3.4.38", 90 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 91 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 92 | "dev": true, 93 | "dependencies": { 94 | "@types/node": "*" 95 | } 96 | }, 97 | "node_modules/@types/cors": { 98 | "version": "2.8.17", 99 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", 100 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", 101 | "dev": true, 102 | "dependencies": { 103 | "@types/node": "*" 104 | } 105 | }, 106 | "node_modules/@types/express": { 107 | "version": "4.17.21", 108 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", 109 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", 110 | "dev": true, 111 | "dependencies": { 112 | "@types/body-parser": "*", 113 | "@types/express-serve-static-core": "^4.17.33", 114 | "@types/qs": "*", 115 | "@types/serve-static": "*" 116 | } 117 | }, 118 | "node_modules/@types/express-serve-static-core": { 119 | "version": "4.19.0", 120 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", 121 | "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", 122 | "dev": true, 123 | "dependencies": { 124 | "@types/node": "*", 125 | "@types/qs": "*", 126 | "@types/range-parser": "*", 127 | "@types/send": "*" 128 | } 129 | }, 130 | "node_modules/@types/http-errors": { 131 | "version": "2.0.4", 132 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 133 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 134 | "dev": true 135 | }, 136 | "node_modules/@types/mime": { 137 | "version": "1.3.5", 138 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 139 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 140 | "dev": true 141 | }, 142 | "node_modules/@types/node": { 143 | "version": "20.12.7", 144 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", 145 | "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", 146 | "dependencies": { 147 | "undici-types": "~5.26.4" 148 | } 149 | }, 150 | "node_modules/@types/qs": { 151 | "version": "6.9.14", 152 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", 153 | "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", 154 | "dev": true 155 | }, 156 | "node_modules/@types/range-parser": { 157 | "version": "1.2.7", 158 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 159 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 160 | "dev": true 161 | }, 162 | "node_modules/@types/send": { 163 | "version": "0.17.4", 164 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 165 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 166 | "dev": true, 167 | "dependencies": { 168 | "@types/mime": "^1", 169 | "@types/node": "*" 170 | } 171 | }, 172 | "node_modules/@types/serve-static": { 173 | "version": "1.15.7", 174 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 175 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 176 | "dev": true, 177 | "dependencies": { 178 | "@types/http-errors": "*", 179 | "@types/node": "*", 180 | "@types/send": "*" 181 | } 182 | }, 183 | "node_modules/accepts": { 184 | "version": "1.3.8", 185 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 186 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 187 | "dependencies": { 188 | "mime-types": "~2.1.34", 189 | "negotiator": "0.6.3" 190 | }, 191 | "engines": { 192 | "node": ">= 0.6" 193 | } 194 | }, 195 | "node_modules/acorn": { 196 | "version": "8.11.3", 197 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 198 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 199 | "bin": { 200 | "acorn": "bin/acorn" 201 | }, 202 | "engines": { 203 | "node": ">=0.4.0" 204 | } 205 | }, 206 | "node_modules/acorn-walk": { 207 | "version": "8.3.2", 208 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 209 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 210 | "engines": { 211 | "node": ">=0.4.0" 212 | } 213 | }, 214 | "node_modules/arg": { 215 | "version": "4.1.3", 216 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 217 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 218 | }, 219 | "node_modules/array-flatten": { 220 | "version": "1.1.1", 221 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 222 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 223 | }, 224 | "node_modules/asynckit": { 225 | "version": "0.4.0", 226 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 227 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 228 | }, 229 | "node_modules/axios": { 230 | "version": "1.6.8", 231 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", 232 | "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", 233 | "dependencies": { 234 | "follow-redirects": "^1.15.6", 235 | "form-data": "^4.0.0", 236 | "proxy-from-env": "^1.1.0" 237 | } 238 | }, 239 | "node_modules/body-parser": { 240 | "version": "1.20.2", 241 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 242 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 243 | "dependencies": { 244 | "bytes": "3.1.2", 245 | "content-type": "~1.0.5", 246 | "debug": "2.6.9", 247 | "depd": "2.0.0", 248 | "destroy": "1.2.0", 249 | "http-errors": "2.0.0", 250 | "iconv-lite": "0.4.24", 251 | "on-finished": "2.4.1", 252 | "qs": "6.11.0", 253 | "raw-body": "2.5.2", 254 | "type-is": "~1.6.18", 255 | "unpipe": "1.0.0" 256 | }, 257 | "engines": { 258 | "node": ">= 0.8", 259 | "npm": "1.2.8000 || >= 1.4.16" 260 | } 261 | }, 262 | "node_modules/boolbase": { 263 | "version": "1.0.0", 264 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 265 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" 266 | }, 267 | "node_modules/bytes": { 268 | "version": "3.1.2", 269 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 270 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 271 | "engines": { 272 | "node": ">= 0.8" 273 | } 274 | }, 275 | "node_modules/call-bind": { 276 | "version": "1.0.7", 277 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 278 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 279 | "dependencies": { 280 | "es-define-property": "^1.0.0", 281 | "es-errors": "^1.3.0", 282 | "function-bind": "^1.1.2", 283 | "get-intrinsic": "^1.2.4", 284 | "set-function-length": "^1.2.1" 285 | }, 286 | "engines": { 287 | "node": ">= 0.4" 288 | }, 289 | "funding": { 290 | "url": "https://github.com/sponsors/ljharb" 291 | } 292 | }, 293 | "node_modules/cheerio": { 294 | "version": "1.0.0-rc.12", 295 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", 296 | "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", 297 | "dependencies": { 298 | "cheerio-select": "^2.1.0", 299 | "dom-serializer": "^2.0.0", 300 | "domhandler": "^5.0.3", 301 | "domutils": "^3.0.1", 302 | "htmlparser2": "^8.0.1", 303 | "parse5": "^7.0.0", 304 | "parse5-htmlparser2-tree-adapter": "^7.0.0" 305 | }, 306 | "engines": { 307 | "node": ">= 6" 308 | }, 309 | "funding": { 310 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1" 311 | } 312 | }, 313 | "node_modules/cheerio-select": { 314 | "version": "2.1.0", 315 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", 316 | "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", 317 | "dependencies": { 318 | "boolbase": "^1.0.0", 319 | "css-select": "^5.1.0", 320 | "css-what": "^6.1.0", 321 | "domelementtype": "^2.3.0", 322 | "domhandler": "^5.0.3", 323 | "domutils": "^3.0.1" 324 | }, 325 | "funding": { 326 | "url": "https://github.com/sponsors/fb55" 327 | } 328 | }, 329 | "node_modules/combined-stream": { 330 | "version": "1.0.8", 331 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 332 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 333 | "dependencies": { 334 | "delayed-stream": "~1.0.0" 335 | }, 336 | "engines": { 337 | "node": ">= 0.8" 338 | } 339 | }, 340 | "node_modules/content-disposition": { 341 | "version": "0.5.4", 342 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 343 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 344 | "dependencies": { 345 | "safe-buffer": "5.2.1" 346 | }, 347 | "engines": { 348 | "node": ">= 0.6" 349 | } 350 | }, 351 | "node_modules/content-type": { 352 | "version": "1.0.5", 353 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 354 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 355 | "engines": { 356 | "node": ">= 0.6" 357 | } 358 | }, 359 | "node_modules/cookie": { 360 | "version": "0.6.0", 361 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 362 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 363 | "engines": { 364 | "node": ">= 0.6" 365 | } 366 | }, 367 | "node_modules/cookie-signature": { 368 | "version": "1.0.6", 369 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 370 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 371 | }, 372 | "node_modules/cors": { 373 | "version": "2.8.5", 374 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 375 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 376 | "dependencies": { 377 | "object-assign": "^4", 378 | "vary": "^1" 379 | }, 380 | "engines": { 381 | "node": ">= 0.10" 382 | } 383 | }, 384 | "node_modules/create-require": { 385 | "version": "1.1.1", 386 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 387 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 388 | }, 389 | "node_modules/css-select": { 390 | "version": "5.1.0", 391 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", 392 | "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", 393 | "dependencies": { 394 | "boolbase": "^1.0.0", 395 | "css-what": "^6.1.0", 396 | "domhandler": "^5.0.2", 397 | "domutils": "^3.0.1", 398 | "nth-check": "^2.0.1" 399 | }, 400 | "funding": { 401 | "url": "https://github.com/sponsors/fb55" 402 | } 403 | }, 404 | "node_modules/css-what": { 405 | "version": "6.1.0", 406 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", 407 | "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", 408 | "engines": { 409 | "node": ">= 6" 410 | }, 411 | "funding": { 412 | "url": "https://github.com/sponsors/fb55" 413 | } 414 | }, 415 | "node_modules/debug": { 416 | "version": "2.6.9", 417 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 418 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 419 | "dependencies": { 420 | "ms": "2.0.0" 421 | } 422 | }, 423 | "node_modules/define-data-property": { 424 | "version": "1.1.4", 425 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 426 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 427 | "dependencies": { 428 | "es-define-property": "^1.0.0", 429 | "es-errors": "^1.3.0", 430 | "gopd": "^1.0.1" 431 | }, 432 | "engines": { 433 | "node": ">= 0.4" 434 | }, 435 | "funding": { 436 | "url": "https://github.com/sponsors/ljharb" 437 | } 438 | }, 439 | "node_modules/delayed-stream": { 440 | "version": "1.0.0", 441 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 442 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 443 | "engines": { 444 | "node": ">=0.4.0" 445 | } 446 | }, 447 | "node_modules/depd": { 448 | "version": "2.0.0", 449 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 450 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 451 | "engines": { 452 | "node": ">= 0.8" 453 | } 454 | }, 455 | "node_modules/destroy": { 456 | "version": "1.2.0", 457 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 458 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 459 | "engines": { 460 | "node": ">= 0.8", 461 | "npm": "1.2.8000 || >= 1.4.16" 462 | } 463 | }, 464 | "node_modules/diff": { 465 | "version": "4.0.2", 466 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 467 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 468 | "engines": { 469 | "node": ">=0.3.1" 470 | } 471 | }, 472 | "node_modules/dom-serializer": { 473 | "version": "2.0.0", 474 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 475 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 476 | "dependencies": { 477 | "domelementtype": "^2.3.0", 478 | "domhandler": "^5.0.2", 479 | "entities": "^4.2.0" 480 | }, 481 | "funding": { 482 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 483 | } 484 | }, 485 | "node_modules/domelementtype": { 486 | "version": "2.3.0", 487 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 488 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 489 | "funding": [ 490 | { 491 | "type": "github", 492 | "url": "https://github.com/sponsors/fb55" 493 | } 494 | ] 495 | }, 496 | "node_modules/domhandler": { 497 | "version": "5.0.3", 498 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 499 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 500 | "dependencies": { 501 | "domelementtype": "^2.3.0" 502 | }, 503 | "engines": { 504 | "node": ">= 4" 505 | }, 506 | "funding": { 507 | "url": "https://github.com/fb55/domhandler?sponsor=1" 508 | } 509 | }, 510 | "node_modules/domutils": { 511 | "version": "3.1.0", 512 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 513 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 514 | "dependencies": { 515 | "dom-serializer": "^2.0.0", 516 | "domelementtype": "^2.3.0", 517 | "domhandler": "^5.0.3" 518 | }, 519 | "funding": { 520 | "url": "https://github.com/fb55/domutils?sponsor=1" 521 | } 522 | }, 523 | "node_modules/ee-first": { 524 | "version": "1.1.1", 525 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 526 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 527 | }, 528 | "node_modules/encodeurl": { 529 | "version": "1.0.2", 530 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 531 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 532 | "engines": { 533 | "node": ">= 0.8" 534 | } 535 | }, 536 | "node_modules/entities": { 537 | "version": "4.5.0", 538 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 539 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 540 | "engines": { 541 | "node": ">=0.12" 542 | }, 543 | "funding": { 544 | "url": "https://github.com/fb55/entities?sponsor=1" 545 | } 546 | }, 547 | "node_modules/es-define-property": { 548 | "version": "1.0.0", 549 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 550 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 551 | "dependencies": { 552 | "get-intrinsic": "^1.2.4" 553 | }, 554 | "engines": { 555 | "node": ">= 0.4" 556 | } 557 | }, 558 | "node_modules/es-errors": { 559 | "version": "1.3.0", 560 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 561 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 562 | "engines": { 563 | "node": ">= 0.4" 564 | } 565 | }, 566 | "node_modules/escape-html": { 567 | "version": "1.0.3", 568 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 569 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 570 | }, 571 | "node_modules/etag": { 572 | "version": "1.8.1", 573 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 574 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 575 | "engines": { 576 | "node": ">= 0.6" 577 | } 578 | }, 579 | "node_modules/express": { 580 | "version": "4.19.2", 581 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 582 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 583 | "dependencies": { 584 | "accepts": "~1.3.8", 585 | "array-flatten": "1.1.1", 586 | "body-parser": "1.20.2", 587 | "content-disposition": "0.5.4", 588 | "content-type": "~1.0.4", 589 | "cookie": "0.6.0", 590 | "cookie-signature": "1.0.6", 591 | "debug": "2.6.9", 592 | "depd": "2.0.0", 593 | "encodeurl": "~1.0.2", 594 | "escape-html": "~1.0.3", 595 | "etag": "~1.8.1", 596 | "finalhandler": "1.2.0", 597 | "fresh": "0.5.2", 598 | "http-errors": "2.0.0", 599 | "merge-descriptors": "1.0.1", 600 | "methods": "~1.1.2", 601 | "on-finished": "2.4.1", 602 | "parseurl": "~1.3.3", 603 | "path-to-regexp": "0.1.7", 604 | "proxy-addr": "~2.0.7", 605 | "qs": "6.11.0", 606 | "range-parser": "~1.2.1", 607 | "safe-buffer": "5.2.1", 608 | "send": "0.18.0", 609 | "serve-static": "1.15.0", 610 | "setprototypeof": "1.2.0", 611 | "statuses": "2.0.1", 612 | "type-is": "~1.6.18", 613 | "utils-merge": "1.0.1", 614 | "vary": "~1.1.2" 615 | }, 616 | "engines": { 617 | "node": ">= 0.10.0" 618 | } 619 | }, 620 | "node_modules/finalhandler": { 621 | "version": "1.2.0", 622 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 623 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 624 | "dependencies": { 625 | "debug": "2.6.9", 626 | "encodeurl": "~1.0.2", 627 | "escape-html": "~1.0.3", 628 | "on-finished": "2.4.1", 629 | "parseurl": "~1.3.3", 630 | "statuses": "2.0.1", 631 | "unpipe": "~1.0.0" 632 | }, 633 | "engines": { 634 | "node": ">= 0.8" 635 | } 636 | }, 637 | "node_modules/follow-redirects": { 638 | "version": "1.15.6", 639 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 640 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 641 | "funding": [ 642 | { 643 | "type": "individual", 644 | "url": "https://github.com/sponsors/RubenVerborgh" 645 | } 646 | ], 647 | "engines": { 648 | "node": ">=4.0" 649 | }, 650 | "peerDependenciesMeta": { 651 | "debug": { 652 | "optional": true 653 | } 654 | } 655 | }, 656 | "node_modules/form-data": { 657 | "version": "4.0.0", 658 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 659 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 660 | "dependencies": { 661 | "asynckit": "^0.4.0", 662 | "combined-stream": "^1.0.8", 663 | "mime-types": "^2.1.12" 664 | }, 665 | "engines": { 666 | "node": ">= 6" 667 | } 668 | }, 669 | "node_modules/forwarded": { 670 | "version": "0.2.0", 671 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 672 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 673 | "engines": { 674 | "node": ">= 0.6" 675 | } 676 | }, 677 | "node_modules/fresh": { 678 | "version": "0.5.2", 679 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 680 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 681 | "engines": { 682 | "node": ">= 0.6" 683 | } 684 | }, 685 | "node_modules/function-bind": { 686 | "version": "1.1.2", 687 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 688 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 689 | "funding": { 690 | "url": "https://github.com/sponsors/ljharb" 691 | } 692 | }, 693 | "node_modules/get-intrinsic": { 694 | "version": "1.2.4", 695 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 696 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 697 | "dependencies": { 698 | "es-errors": "^1.3.0", 699 | "function-bind": "^1.1.2", 700 | "has-proto": "^1.0.1", 701 | "has-symbols": "^1.0.3", 702 | "hasown": "^2.0.0" 703 | }, 704 | "engines": { 705 | "node": ">= 0.4" 706 | }, 707 | "funding": { 708 | "url": "https://github.com/sponsors/ljharb" 709 | } 710 | }, 711 | "node_modules/gopd": { 712 | "version": "1.0.1", 713 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 714 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 715 | "dependencies": { 716 | "get-intrinsic": "^1.1.3" 717 | }, 718 | "funding": { 719 | "url": "https://github.com/sponsors/ljharb" 720 | } 721 | }, 722 | "node_modules/has-property-descriptors": { 723 | "version": "1.0.2", 724 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 725 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 726 | "dependencies": { 727 | "es-define-property": "^1.0.0" 728 | }, 729 | "funding": { 730 | "url": "https://github.com/sponsors/ljharb" 731 | } 732 | }, 733 | "node_modules/has-proto": { 734 | "version": "1.0.3", 735 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 736 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 737 | "engines": { 738 | "node": ">= 0.4" 739 | }, 740 | "funding": { 741 | "url": "https://github.com/sponsors/ljharb" 742 | } 743 | }, 744 | "node_modules/has-symbols": { 745 | "version": "1.0.3", 746 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 747 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 748 | "engines": { 749 | "node": ">= 0.4" 750 | }, 751 | "funding": { 752 | "url": "https://github.com/sponsors/ljharb" 753 | } 754 | }, 755 | "node_modules/hasown": { 756 | "version": "2.0.2", 757 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 758 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 759 | "dependencies": { 760 | "function-bind": "^1.1.2" 761 | }, 762 | "engines": { 763 | "node": ">= 0.4" 764 | } 765 | }, 766 | "node_modules/htmlparser2": { 767 | "version": "8.0.2", 768 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", 769 | "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", 770 | "funding": [ 771 | "https://github.com/fb55/htmlparser2?sponsor=1", 772 | { 773 | "type": "github", 774 | "url": "https://github.com/sponsors/fb55" 775 | } 776 | ], 777 | "dependencies": { 778 | "domelementtype": "^2.3.0", 779 | "domhandler": "^5.0.3", 780 | "domutils": "^3.0.1", 781 | "entities": "^4.4.0" 782 | } 783 | }, 784 | "node_modules/http-errors": { 785 | "version": "2.0.0", 786 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 787 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 788 | "dependencies": { 789 | "depd": "2.0.0", 790 | "inherits": "2.0.4", 791 | "setprototypeof": "1.2.0", 792 | "statuses": "2.0.1", 793 | "toidentifier": "1.0.1" 794 | }, 795 | "engines": { 796 | "node": ">= 0.8" 797 | } 798 | }, 799 | "node_modules/iconv-lite": { 800 | "version": "0.4.24", 801 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 802 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 803 | "dependencies": { 804 | "safer-buffer": ">= 2.1.2 < 3" 805 | }, 806 | "engines": { 807 | "node": ">=0.10.0" 808 | } 809 | }, 810 | "node_modules/inherits": { 811 | "version": "2.0.4", 812 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 813 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 814 | }, 815 | "node_modules/ipaddr.js": { 816 | "version": "1.9.1", 817 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 818 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 819 | "engines": { 820 | "node": ">= 0.10" 821 | } 822 | }, 823 | "node_modules/make-error": { 824 | "version": "1.3.6", 825 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 826 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 827 | }, 828 | "node_modules/media-typer": { 829 | "version": "0.3.0", 830 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 831 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 832 | "engines": { 833 | "node": ">= 0.6" 834 | } 835 | }, 836 | "node_modules/merge-descriptors": { 837 | "version": "1.0.1", 838 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 839 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 840 | }, 841 | "node_modules/methods": { 842 | "version": "1.1.2", 843 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 844 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 845 | "engines": { 846 | "node": ">= 0.6" 847 | } 848 | }, 849 | "node_modules/mime": { 850 | "version": "1.6.0", 851 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 852 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 853 | "bin": { 854 | "mime": "cli.js" 855 | }, 856 | "engines": { 857 | "node": ">=4" 858 | } 859 | }, 860 | "node_modules/mime-db": { 861 | "version": "1.52.0", 862 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 863 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 864 | "engines": { 865 | "node": ">= 0.6" 866 | } 867 | }, 868 | "node_modules/mime-types": { 869 | "version": "2.1.35", 870 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 871 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 872 | "dependencies": { 873 | "mime-db": "1.52.0" 874 | }, 875 | "engines": { 876 | "node": ">= 0.6" 877 | } 878 | }, 879 | "node_modules/ms": { 880 | "version": "2.0.0", 881 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 882 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 883 | }, 884 | "node_modules/negotiator": { 885 | "version": "0.6.3", 886 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 887 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 888 | "engines": { 889 | "node": ">= 0.6" 890 | } 891 | }, 892 | "node_modules/nth-check": { 893 | "version": "2.1.1", 894 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", 895 | "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", 896 | "dependencies": { 897 | "boolbase": "^1.0.0" 898 | }, 899 | "funding": { 900 | "url": "https://github.com/fb55/nth-check?sponsor=1" 901 | } 902 | }, 903 | "node_modules/object-assign": { 904 | "version": "4.1.1", 905 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 906 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 907 | "engines": { 908 | "node": ">=0.10.0" 909 | } 910 | }, 911 | "node_modules/object-inspect": { 912 | "version": "1.13.1", 913 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 914 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 915 | "funding": { 916 | "url": "https://github.com/sponsors/ljharb" 917 | } 918 | }, 919 | "node_modules/on-finished": { 920 | "version": "2.4.1", 921 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 922 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 923 | "dependencies": { 924 | "ee-first": "1.1.1" 925 | }, 926 | "engines": { 927 | "node": ">= 0.8" 928 | } 929 | }, 930 | "node_modules/parse5": { 931 | "version": "7.1.2", 932 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", 933 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", 934 | "dependencies": { 935 | "entities": "^4.4.0" 936 | }, 937 | "funding": { 938 | "url": "https://github.com/inikulin/parse5?sponsor=1" 939 | } 940 | }, 941 | "node_modules/parse5-htmlparser2-tree-adapter": { 942 | "version": "7.0.0", 943 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", 944 | "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", 945 | "dependencies": { 946 | "domhandler": "^5.0.2", 947 | "parse5": "^7.0.0" 948 | }, 949 | "funding": { 950 | "url": "https://github.com/inikulin/parse5?sponsor=1" 951 | } 952 | }, 953 | "node_modules/parseurl": { 954 | "version": "1.3.3", 955 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 956 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 957 | "engines": { 958 | "node": ">= 0.8" 959 | } 960 | }, 961 | "node_modules/path-to-regexp": { 962 | "version": "0.1.7", 963 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 964 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 965 | }, 966 | "node_modules/proxy-addr": { 967 | "version": "2.0.7", 968 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 969 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 970 | "dependencies": { 971 | "forwarded": "0.2.0", 972 | "ipaddr.js": "1.9.1" 973 | }, 974 | "engines": { 975 | "node": ">= 0.10" 976 | } 977 | }, 978 | "node_modules/proxy-from-env": { 979 | "version": "1.1.0", 980 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 981 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 982 | }, 983 | "node_modules/qs": { 984 | "version": "6.11.0", 985 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 986 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 987 | "dependencies": { 988 | "side-channel": "^1.0.4" 989 | }, 990 | "engines": { 991 | "node": ">=0.6" 992 | }, 993 | "funding": { 994 | "url": "https://github.com/sponsors/ljharb" 995 | } 996 | }, 997 | "node_modules/range-parser": { 998 | "version": "1.2.1", 999 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1000 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1001 | "engines": { 1002 | "node": ">= 0.6" 1003 | } 1004 | }, 1005 | "node_modules/raw-body": { 1006 | "version": "2.5.2", 1007 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1008 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1009 | "dependencies": { 1010 | "bytes": "3.1.2", 1011 | "http-errors": "2.0.0", 1012 | "iconv-lite": "0.4.24", 1013 | "unpipe": "1.0.0" 1014 | }, 1015 | "engines": { 1016 | "node": ">= 0.8" 1017 | } 1018 | }, 1019 | "node_modules/safe-buffer": { 1020 | "version": "5.2.1", 1021 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1022 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1023 | "funding": [ 1024 | { 1025 | "type": "github", 1026 | "url": "https://github.com/sponsors/feross" 1027 | }, 1028 | { 1029 | "type": "patreon", 1030 | "url": "https://www.patreon.com/feross" 1031 | }, 1032 | { 1033 | "type": "consulting", 1034 | "url": "https://feross.org/support" 1035 | } 1036 | ] 1037 | }, 1038 | "node_modules/safer-buffer": { 1039 | "version": "2.1.2", 1040 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1041 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1042 | }, 1043 | "node_modules/send": { 1044 | "version": "0.18.0", 1045 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1046 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1047 | "dependencies": { 1048 | "debug": "2.6.9", 1049 | "depd": "2.0.0", 1050 | "destroy": "1.2.0", 1051 | "encodeurl": "~1.0.2", 1052 | "escape-html": "~1.0.3", 1053 | "etag": "~1.8.1", 1054 | "fresh": "0.5.2", 1055 | "http-errors": "2.0.0", 1056 | "mime": "1.6.0", 1057 | "ms": "2.1.3", 1058 | "on-finished": "2.4.1", 1059 | "range-parser": "~1.2.1", 1060 | "statuses": "2.0.1" 1061 | }, 1062 | "engines": { 1063 | "node": ">= 0.8.0" 1064 | } 1065 | }, 1066 | "node_modules/send/node_modules/ms": { 1067 | "version": "2.1.3", 1068 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1069 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1070 | }, 1071 | "node_modules/serve-static": { 1072 | "version": "1.15.0", 1073 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1074 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1075 | "dependencies": { 1076 | "encodeurl": "~1.0.2", 1077 | "escape-html": "~1.0.3", 1078 | "parseurl": "~1.3.3", 1079 | "send": "0.18.0" 1080 | }, 1081 | "engines": { 1082 | "node": ">= 0.8.0" 1083 | } 1084 | }, 1085 | "node_modules/set-function-length": { 1086 | "version": "1.2.2", 1087 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1088 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1089 | "dependencies": { 1090 | "define-data-property": "^1.1.4", 1091 | "es-errors": "^1.3.0", 1092 | "function-bind": "^1.1.2", 1093 | "get-intrinsic": "^1.2.4", 1094 | "gopd": "^1.0.1", 1095 | "has-property-descriptors": "^1.0.2" 1096 | }, 1097 | "engines": { 1098 | "node": ">= 0.4" 1099 | } 1100 | }, 1101 | "node_modules/setprototypeof": { 1102 | "version": "1.2.0", 1103 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1104 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1105 | }, 1106 | "node_modules/side-channel": { 1107 | "version": "1.0.6", 1108 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 1109 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1110 | "dependencies": { 1111 | "call-bind": "^1.0.7", 1112 | "es-errors": "^1.3.0", 1113 | "get-intrinsic": "^1.2.4", 1114 | "object-inspect": "^1.13.1" 1115 | }, 1116 | "engines": { 1117 | "node": ">= 0.4" 1118 | }, 1119 | "funding": { 1120 | "url": "https://github.com/sponsors/ljharb" 1121 | } 1122 | }, 1123 | "node_modules/statuses": { 1124 | "version": "2.0.1", 1125 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1126 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1127 | "engines": { 1128 | "node": ">= 0.8" 1129 | } 1130 | }, 1131 | "node_modules/toidentifier": { 1132 | "version": "1.0.1", 1133 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1134 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1135 | "engines": { 1136 | "node": ">=0.6" 1137 | } 1138 | }, 1139 | "node_modules/ts-node": { 1140 | "version": "10.9.2", 1141 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 1142 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 1143 | "dependencies": { 1144 | "@cspotcode/source-map-support": "^0.8.0", 1145 | "@tsconfig/node10": "^1.0.7", 1146 | "@tsconfig/node12": "^1.0.7", 1147 | "@tsconfig/node14": "^1.0.0", 1148 | "@tsconfig/node16": "^1.0.2", 1149 | "acorn": "^8.4.1", 1150 | "acorn-walk": "^8.1.1", 1151 | "arg": "^4.1.0", 1152 | "create-require": "^1.1.0", 1153 | "diff": "^4.0.1", 1154 | "make-error": "^1.1.1", 1155 | "v8-compile-cache-lib": "^3.0.1", 1156 | "yn": "3.1.1" 1157 | }, 1158 | "bin": { 1159 | "ts-node": "dist/bin.js", 1160 | "ts-node-cwd": "dist/bin-cwd.js", 1161 | "ts-node-esm": "dist/bin-esm.js", 1162 | "ts-node-script": "dist/bin-script.js", 1163 | "ts-node-transpile-only": "dist/bin-transpile.js", 1164 | "ts-script": "dist/bin-script-deprecated.js" 1165 | }, 1166 | "peerDependencies": { 1167 | "@swc/core": ">=1.2.50", 1168 | "@swc/wasm": ">=1.2.50", 1169 | "@types/node": "*", 1170 | "typescript": ">=2.7" 1171 | }, 1172 | "peerDependenciesMeta": { 1173 | "@swc/core": { 1174 | "optional": true 1175 | }, 1176 | "@swc/wasm": { 1177 | "optional": true 1178 | } 1179 | } 1180 | }, 1181 | "node_modules/type-is": { 1182 | "version": "1.6.18", 1183 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1184 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1185 | "dependencies": { 1186 | "media-typer": "0.3.0", 1187 | "mime-types": "~2.1.24" 1188 | }, 1189 | "engines": { 1190 | "node": ">= 0.6" 1191 | } 1192 | }, 1193 | "node_modules/typescript": { 1194 | "version": "5.4.5", 1195 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", 1196 | "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", 1197 | "bin": { 1198 | "tsc": "bin/tsc", 1199 | "tsserver": "bin/tsserver" 1200 | }, 1201 | "engines": { 1202 | "node": ">=14.17" 1203 | } 1204 | }, 1205 | "node_modules/undici-types": { 1206 | "version": "5.26.5", 1207 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1208 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 1209 | }, 1210 | "node_modules/unpipe": { 1211 | "version": "1.0.0", 1212 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1213 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1214 | "engines": { 1215 | "node": ">= 0.8" 1216 | } 1217 | }, 1218 | "node_modules/utils-merge": { 1219 | "version": "1.0.1", 1220 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1221 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1222 | "engines": { 1223 | "node": ">= 0.4.0" 1224 | } 1225 | }, 1226 | "node_modules/v8-compile-cache-lib": { 1227 | "version": "3.0.1", 1228 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1229 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 1230 | }, 1231 | "node_modules/vary": { 1232 | "version": "1.1.2", 1233 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1234 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1235 | "engines": { 1236 | "node": ">= 0.8" 1237 | } 1238 | }, 1239 | "node_modules/yn": { 1240 | "version": "3.1.1", 1241 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1242 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1243 | "engines": { 1244 | "node": ">=6" 1245 | } 1246 | } 1247 | } 1248 | } 1249 | --------------------------------------------------------------------------------