diff --git a/leetcode-daily-card/SKILL.md b/leetcode-daily-card/SKILL.md index ef86faa..325c770 100644 --- a/leetcode-daily-card/SKILL.md +++ b/leetcode-daily-card/SKILL.md @@ -1,49 +1,69 @@ --- name: leetcode-daily-card -description: A Python script that automatically fetches the daily LeetCode challenge and renders it as a PNG card or Markdown document. +description: A Node.js script that fetches the daily LeetCode challenge and renders it as a PNG card, HTML page, or Markdown document. metadata: {"clawdbot":{"emoji":"📋","os":["linux","darwin","win32"]}} --- # LeetCode Daily Card Generator -A Python script that automatically fetches the daily LeetCode challenge and renders it as a PNG card or Markdown document. +A Node.js script that automatically fetches the daily LeetCode challenge and renders it as a PNG card, HTML page, or Markdown document. ## Features - **Automatic Daily Fetch**: Retrieves the current daily LeetCode problem via GraphQL API -- **Two-Step Rendering**: HTML → Markdown → PNG for optimal text rendering -- **CJK Support**: Uses system CJK fonts (Microsoft YaHei, SimHei, etc.) for Chinese characters -- **Multiple Output Formats**: Supports `.png` for image cards and `.md` for Markdown documents -- **Clean Unicode Handling**: Removes zero-width spaces and other invisible Unicode characters +- **Browser Rendering**: Uses Puppeteer for high-quality PNG output with CSS styling +- **Multiple Output Formats**: `.png` (image), `.html` (styled page), `.md` (markdown) +- **Theme Support**: Light and dark themes +- **CJK Support**: Noto Sans SC and system fonts for Chinese characters +- **Code Highlighting**: Syntax highlighting via highlight.js +- **LaTeX Support**: KaTeX rendering for math formulas ## Installation ```bash -pip install Pillow html2text +cd scripts +npm install ``` ## Dependencies -- **Pillow**: For PNG/JPG image rendering -- **html2text**: For converting HTML content to Markdown +- **puppeteer-core**: Browser automation (uses local Chrome/Edge) +- **marked**: Markdown parsing +- **katex**: LaTeX math rendering +- **highlight.js**: Code syntax highlighting ## Usage ```bash -# Generate PNG card -python scripts/run.py output.png +# Generate PNG card (default light theme) +node run.js output.png + +# Generate PNG with dark theme +node run.js output.png --dark + +# Generate HTML page +node run.js output.html # Generate Markdown document -python scripts/run.py output.md +node run.js output.md + +# Custom max width (for long descriptions) +node run.js output.png --max-width=800 --dark ``` ## Output Formats ### PNG Card -- Blue gradient header -- White card body with rounded corners -- Dark gradient background -- Displays: title, question ID, difficulty, acceptance rate, tags, description +- Gradient header with date and LeetCode icon +- Question title, ID, difficulty badge, acceptance rate +- Tags as styled pills +- Full problem description with code formatting +- Footer with link to problem page + +### HTML Page +- Same styling as PNG, exportable as standalone file +- Responsive design +- Interactive links ### Markdown Document - Clean Markdown formatting @@ -54,34 +74,37 @@ python scripts/run.py output.md ## Supported Platforms -- **Windows**: Uses `msyh.ttc` (Microsoft YaHei), `simhei.ttf`, etc. -- **macOS**: Uses PingFang.ttc, STHeiti Light.ttc -- **Linux**: Uses NotoSansCJK, DejaVuSans.ttf, wqy-microhei.ttc +- **Windows**: Chrome/Edge from default installation paths +- **macOS**: Chrome/Edge from Applications +- **Linux**: Chrome/Chromium from system PATH ## Workflow ``` -LeetCode API (HTML) +LeetCode GraphQL API ↓ -html2text (Markdown) +Fetch Question Data ↓ -Markdown → Plain Text (cleanup) +Generate HTML Template ↓ -Pillow (PNG Image) +Browser Rendering (PNG) / File Output (HTML/MD) ``` -## Example +## Browser Configuration + +The script auto-detects installed browsers in this order: +1. `CHROME_PATH` environment variable +2. Google Chrome +3. Microsoft Edge +4. Chromium ```bash -python scripts/run.py daily.png -# Output: daily.png with the daily LeetCode challenge card - -python scripts/run.py daily.md -# Output: daily.md with formatted Markdown content +# Force specific browser +CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" node run.js output.png ``` ## Notes -- The script requires internet access to fetch from LeetCode API -- Formula rendering is not supported (plain text only) -- Chinese fonts must be installed on the system for CJK character display \ No newline at end of file +- Requires internet access to fetch from LeetCode API +- Browser must be installed for PNG/HTML output +- Markdown output works without browser diff --git a/leetcode-daily-card/scripts/package-lock.json b/leetcode-daily-card/scripts/package-lock.json new file mode 100644 index 0000000..fa8e02b --- /dev/null +++ b/leetcode-daily-card/scripts/package-lock.json @@ -0,0 +1,1050 @@ +{ + "name": "leetcode-daily-card", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "leetcode-daily-card", + "version": "1.0.0", + "dependencies": { + "katex": "^0.16.9", + "marked": "^12.0.0", + "puppeteer-core": "^22.15.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmmirror.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/bare-os/-/bare-os-3.9.0.tgz", + "integrity": "sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.0", + "resolved": "https://registry.npmmirror.com/bare-stream/-/bare-stream-2.13.0.tgz", + "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/bare-url/-/bare-url-2.4.2.tgz", + "integrity": "sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/basic-ftp/-/basic-ftp-5.3.0.tgz", + "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmmirror.com/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmmirror.com/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/katex": { + "version": "0.16.45", + "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.45.tgz", + "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmmirror.com/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT", + "optional": true + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/leetcode-daily-card/scripts/package.json b/leetcode-daily-card/scripts/package.json new file mode 100644 index 0000000..973d760 --- /dev/null +++ b/leetcode-daily-card/scripts/package.json @@ -0,0 +1,14 @@ +{ + "name": "leetcode-daily-card", + "version": "1.0.0", + "description": "LeetCode Daily Challenge Card Renderer", + "scripts": { + "start": "node run.js", + "render": "node run.js" + }, + "dependencies": { + "katex": "^0.16.9", + "marked": "^12.0.0", + "puppeteer-core": "^22.15.0" + } +} diff --git a/leetcode-daily-card/scripts/run.js b/leetcode-daily-card/scripts/run.js new file mode 100644 index 0000000..5b765eb --- /dev/null +++ b/leetcode-daily-card/scripts/run.js @@ -0,0 +1,391 @@ +/** + * LeetCode Daily Card - Pure Node.js Version + * + * Usage: + * node run.js [output.png|output.md|output.html] [--dark|--light] [--width=600] + * + * Dependencies: puppeteer-core, marked, katex + */ + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { marked } = require('marked'); +const katex = require('katex'); + +// === CLI Args === +const args = process.argv.slice(2); +let outputPath = null; +let forcedTheme = null; +let maxWidth = 900; + +for (const arg of args) { + if (arg.startsWith('--max-width=')) maxWidth = parseInt(arg.split('=')[1]); + else if (arg === '--dark') forcedTheme = 'dark'; + else if (arg === '--light') forcedTheme = 'light'; + else if (!outputPath) outputPath = arg; +} + +if (!outputPath) { + console.error('Usage: node run.js [--dark|--light] [--max-width=900]'); + process.exit(1); +} + +outputPath = path.resolve(outputPath); +const ext = path.extname(outputPath).toLowerCase(); + +// === GraphQL Query === +const GRAPHQL_QUERY = `query questionOfToday { + activeDailyCodingChallengeQuestion { + date + question { + questionId + title + titleSlug + difficulty + content + acRate + topicTags { name slug } + exampleTestcases + } + } +}`; + +// === Fetch Data === +function fetchDailyChallenge() { + return new Promise((resolve, reject) => { + const data = JSON.stringify({ query: GRAPHQL_QUERY }); + const options = { + hostname: 'leetcode.com', + path: '/graphql/', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data), + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + }; + + const req = https.request(options, (res) => { + let body = ''; + res.on('data', chunk => body += chunk); + res.on('end', () => { + try { + const json = JSON.parse(body); + const challenge = json.data?.activeDailyCodingChallengeQuestion; + if (!challenge) return reject(new Error('No daily challenge found')); + + const q = challenge.question; + resolve({ + date: challenge.date, + title: q.title, + title_slug: q.titleSlug, + question_id: q.questionId, + difficulty: q.difficulty, + content: q.content || '', + tags: (q.topicTags || []).map(t => t.name), + example_cases: q.exampleTestcases || '', + ac_rate: q.acRate || 0 + }); + } catch (e) { + reject(e); + } + }); + }); + req.on('error', reject); + req.write(data); + req.end(); + }); +} + +// === HTML to Markdown === +function htmlToMarkdown(html) { + // Clean invisible unicode + html = html.replace(/[\u200b-\u200f\u2028-\u202f\ufeff\u00ad]/g, ''); + html = html.replace(/[\xa0\u3000]/g, ' '); + html = html.replace(/[\uff00-\uffef]/g, ''); + + // Use marked to parse HTML content + return marked.parse(html); +} + +// === Build HTML Template === +function buildHtml(data, theme = 'light') { + const isDark = theme === 'dark'; + + const diffStyles = { + 'Easy': { label: 'Easy', bg: '#dcfce7', fg: '#166534' }, + 'Medium': { label: 'Medium', bg: '#fef9c3', fg: '#854d0e' }, + 'Hard': { label: 'Hard', bg: '#fee2e2', fg: '#991b1b' }, + }; + const ds = diffStyles[data.difficulty] || { label: data.difficulty, bg: '#e5e7eb', fg: '#374151' }; + + // Convert HTML content to markdown then to HTML + const descMd = data.content ? htmlToMarkdown(data.content) : ''; + const descHtml = marked.parse(descMd); + + const tagsHtml = data.tags.length + ? '
' + data.tags.map(t => `${t}`).join('') + '
' + : ''; + + return ` + + + + + + + + + + +
+
+
+ ${data.date} + 📋 +
+

${data.title}

+
+ #${data.question_id} + ${ds.label} + Accept: ${data.ac_rate.toFixed(1)}% +
+
+
+ ${tagsHtml} +
+${descHtml} +
+
+ +
+ +`; +} + +// === Build Markdown === +function buildMarkdown(data) { + const tagsMd = data.tags.length ? data.tags.map(t => `- \`${t}\``).join('\n') : ''; + const descMd = data.content ? htmlToMarkdown(data.content) : ''; + + return `# ${data.title} + +## Info +- **#${data.question_id}** | ${data.difficulty} | Accept Rate: ${data.ac_rate.toFixed(1)}% +- Date: ${data.date} +- Tags: +${tagsMd} + +## Description +${descMd} + +## Examples +\`\`\` +${data.example_cases} +\`\`\` + +## Link +[View on LeetCode](https://leetcode.com/problems/${data.title_slug}/) +`; +} + +// === Browser Rendering === +function findBrowserPath() { + if (process.env.CHROME_PATH && fs.existsSync(process.env.CHROME_PATH)) { + return process.env.CHROME_PATH; + } + const candidates = { + win32: [ + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', + 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe', + ], + darwin: [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', + ], + linux: ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'], + }; + for (const p of (candidates[process.platform] || [])) { + if (fs.existsSync(p)) return p; + } + return null; +} + +async function getBrowser() { + let browserModule, usingCore = false; + try { + browserModule = require('puppeteer-core'); + usingCore = true; + } catch { + try { + browserModule = require('puppeteer'); + } catch { + console.error('Error: Neither puppeteer-core nor puppeteer is installed.'); + console.error('Run: cd scripts && npm install puppeteer-core'); + process.exit(1); + } + } + + const browserPath = findBrowserPath(); + if (usingCore) { + if (!browserPath) { + console.error('Error: puppeteer-core requires Chrome/Edge/Chromium.'); + console.error('Set CHROME_PATH env var or install a browser.'); + process.exit(1); + } + console.log(`Using: puppeteer-core (${path.basename(browserPath)})`); + return browserModule.launch({ + headless: 'new', + executablePath: browserPath, + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + } + console.log('Using: puppeteer (bundled Chromium)'); + return browserModule.launch({ headless: 'new', args: ['--no-sandbox'] }); +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function renderToPng(html, outputPath) { + const browser = await getBrowser(); + try { + const page = await browser.newPage(); + await page.setViewport({ width: maxWidth, height: 800, deviceScaleFactor: 2 }); + + await page.setContent(html, { waitUntil: 'networkidle0' }); + await sleep(1500); + + const bodyHeight = await page.evaluate(() => document.body.scrollHeight); + + await page.setViewport({ + width: maxWidth, + height: Math.max(bodyHeight, 200), + deviceScaleFactor: 2, + }); + + await page.screenshot({ + path: outputPath, + fullPage: true, + type: 'png', + }); + + console.log(`Done: ${outputPath}`); + const stats = fs.statSync(outputPath); + console.log(`Size: ${(stats.size / 1024).toFixed(1)} KB`); + console.log(`Dimensions: ${maxWidth} x ${bodyHeight} px (2x scale)`); + } finally { + await browser.close(); + } +} + +// === Main === +async function main() { + console.log('Fetching LeetCode daily challenge...'); + const data = await fetchDailyChallenge(); + console.log(`Question: #${data.question_id} - ${data.title} [${data.difficulty}]`); + + const theme = forcedTheme || 'light'; + console.log(`Theme: ${theme}, Max Width: ${maxWidth}px`); + + if (ext === '.md') { + const md = buildMarkdown(data); + fs.writeFileSync(outputPath, md, 'utf-8'); + console.log(`Done: ${outputPath}`); + } else if (ext === '.html') { + const html = buildHtml(data, theme); + fs.writeFileSync(outputPath, html, 'utf-8'); + console.log(`Done: ${outputPath}`); + } else { + const html = buildHtml(data, theme); + await renderToPng(html, outputPath); + } +} + +main().catch(err => { + console.error('Error:', err.message || err); + process.exit(1); +}); diff --git a/leetcode-daily-card/scripts/run.py b/leetcode-daily-card/scripts/run.py deleted file mode 100644 index 2ecfb9e..0000000 --- a/leetcode-daily-card/scripts/run.py +++ /dev/null @@ -1,421 +0,0 @@ -"""LeetCode Daily Challenge - Fetch & Render as PNG / MD - -Dependencies: - - PNG output: pip install Pillow - - Markdown output: pip install html2text -""" - -import json -import sys -import os -import urllib.request -import re -from pathlib import Path -import html2text - - -# === GraphQL Query === -GRAPHQL_QUERY = """ -query questionOfToday { - activeDailyCodingChallengeQuestion { - date - question { - questionId - title - titleSlug - difficulty - content - acRate - topicTags { - name - slug - } - exampleTestcases - } - } -} -""" - -# === Fetch Data === -def fetch_daily_challenge(): - url = "https://leetcode.com/graphql/" - payload = json.dumps({"query": GRAPHQL_QUERY}).encode() - req = urllib.request.Request( - url, - data=payload, - headers={ - "Content-Type": "application/json", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", - }, - ) - with urllib.request.urlopen(req) as resp: - data = json.loads(resp.read().decode()) - - challenge = data.get("data", {}).get("activeDailyCodingChallengeQuestion") - if not challenge: - raise RuntimeError("No daily challenge found") - - q = challenge["question"] - return { - "date": challenge["date"], - "title": q["title"], - "title_slug": q["titleSlug"], - "question_id": q["questionId"], - "difficulty": q["difficulty"], - "content": q.get("content", ""), - "tags": [t["name"] for t in (q.get("topicTags") or [])], - "example_cases": q.get("exampleTestcases", ""), - "ac_rate": q.get("acRate", 0), - } - - -# ============================================================ -# Pillow card renderer — zero browser dependency -# ============================================================ - -def _find_font(): - """Find a suitable TrueType font across platforms.""" - candidates = [] - if sys.platform == "win32": - pf = os.environ.get("WINDIR", r"C:\Windows") - candidates = [ - os.path.join(pf, "Fonts", "msyh.ttc"), - os.path.join(pf, "Fonts", "msyhbd.ttc"), - os.path.join(pf, "Fonts", "simhei.ttf"), - os.path.join(pf, "Fonts", "simsun.ttc"), - ] - elif sys.platform == "darwin": - candidates = [ - "/System/Library/Fonts/PingFang.ttc", - "/System/Library/Fonts/STHeiti Light.ttc", - "/Library/Fonts/Arial Unicode.ttf", - ] - else: - candidates = [ - "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", - ] - for p in candidates: - if os.path.exists(p): - return p - return None - - -def _html_to_markdown(html): - """Convert LeetCode content HTML to Markdown using html2text.""" - # 清理所有 Unicode 不可见字符 - html = re.sub(r'[\u200b-\u200f\u2028-\u202f\ufeff\u00ad]', '', html) - html = re.sub(r'[\xa0\u3000]', ' ', html) # non-breaking space, ideographic space - html = re.sub(r'[\uff00-\uffef]', '', html) # halfwidth/fullwidth forms - - h = html2text.HTML2Text() - h.ignore_links = False - h.ignore_images = True - h.body_width = 0 - - md = h.handle(html) - # 清理 markdown 输出中的不可见字符 - md = re.sub(r'[\u200b-\u200f\u2028-\u202f\ufeff]', '', md) - md = re.sub(r'\n{3,}', '\n\n', md) - return md.strip() - - -def _markdown_to_text(md): - """Convert Markdown to plain text for Pillow rendering.""" - if not md: - return "" - - text = md - - # 清理不可见 Unicode 字符 - text = re.sub(r'[\u200b-\u200f\u2028-\u202f\ufeff]', '', text) # zero-width spaces - text = re.sub(r'[\xa0]', ' ', text) # non-breaking space - - # 转换 HTML 实体 - text = text.replace('<', '<') - text = text.replace('>', '>') - text = text.replace('&', '&') - text = text.replace('"', '"') - text = text.replace(''', "'") - text = text.replace(''', "'") - - # 处理特殊引号 - text = text.replace('"', '"').replace('"', '"') - text = text.replace(''', "'").replace(''', "'") - text = text.replace('《', '<').replace('》', '>') - - # 清理 Markdown 格式 - text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) - text = re.sub(r'\*(.*?)*', r'\1', text) - text = re.sub(r'__(.*?)__', r'\1', text) - text = re.sub(r'_(.*?)_', r'\1', text) - text = re.sub(r'`(.*?)`', r'\1', text) - text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE) - text = re.sub(r'^[\-\*+]\s+', '- ', text, flags=re.MULTILINE) - text = re.sub(r'^\d+\.\s+', '- ', text, flags=re.MULTILINE) - text = re.sub(r'\[(.*?)\]\(.*?\)', r'\1', text) - text = re.sub(r'!\[(.*?)\]\(.*?\)', r'\1', text) - text = re.sub(r'^```.*$', '', text, flags=re.MULTILINE) - text = re.sub(r'\n{3,}', '\n\n', text) - - return text.strip() - - -def _wrap_text(text, font, max_width, draw): - """Wrap text to fit within max_width pixels.""" - lines = [] - for paragraph in text.split("\n"): - if not paragraph.strip(): - lines.append("") - continue - current = "" - for ch in paragraph: - test = current + ch - bbox = draw.textbbox((0, 0), test, font=font) - if bbox[2] - bbox[0] > max_width and current: - lines.append(current) - current = ch - else: - current = test - if current: - lines.append(current) - return lines - - -def render_image(data, desc_md, img_path): - """Render card as PNG using only Pillow. - - Args: - data: LeetCode question data dict - desc_md: Pre-converted markdown from HTML - img_path: Output image path - """ - try: - from PIL import Image, ImageDraw, ImageFont - except ImportError: - raise ImportError("Pillow not installed. Run: pip install Pillow") - - font_path = _find_font() - if not font_path: - raise RuntimeError("No CJK font found. Install a Chinese font or set WINDIR/font path.") - - try: - font_title = ImageFont.truetype(font_path, 26) - font_body = ImageFont.truetype(font_path, 16) - font_small = ImageFont.truetype(font_path, 14) - font_tag = ImageFont.truetype(font_path, 13) - font_header = ImageFont.truetype(font_path, 20) - font_icon = ImageFont.truetype(font_path, 17) - except Exception: - font_title = ImageFont.load_default() - font_body = font_small = font_tag = font_header = font_icon = font_title - - W = 540 - PAD = 28 - RADIUS = 20 - HEADER_H = 76 - - # 将 Markdown 转换为纯文本 - desc_text = _markdown_to_text(desc_md) if desc_md else "" - - draw_dummy = ImageDraw.Draw(Image.new("RGB", (W, 100))) - y = PAD - - title_lines = _wrap_text(data["title"], font_title, W - PAD * 2, draw_dummy) - y += len(title_lines) * 34 + 8 - - y += 28 + 16 - - if data["tags"]: - tag_x = PAD - tag_row_h = 32 - for t in data["tags"]: - tw = draw_dummy.textlength(t + " ", font=font_tag) + 24 - if tag_x + tw > W - PAD: - y += tag_row_h + 8 - tag_x = PAD - tag_x += tw + 8 - y += tag_row_h + 22 - - if desc_text: - desc_lines = _wrap_text(desc_text, font_body, W - PAD * 2, draw_dummy) - y += 20 - y += len(desc_lines) * 24 - y += 8 - - TOTAL_H = y + PAD - TOTAL_H = max(TOTAL_H, 400) - - img = Image.new("RGB", (W, TOTAL_H), "#1a1a2e") - draw = ImageDraw.Draw(img) - - for row in range(TOTAL_H): - r = int(15 + (48 - 15) * row / TOTAL_H) - g = int(12 + (43 - 12) * row / TOTAL_H) - b = int(41 + (62 - 41) * row / TOTAL_H) - draw.line([(0, row), (W, row)], fill=(r, g, b)) - - card_top = HEADER_H - draw.rounded_rectangle( - [0, card_top, W - 1, TOTAL_H - 1], - radius=RADIUS, - fill="#ffffff", - ) - draw.rectangle([0, card_top, W - 1, card_top + RADIUS], fill="#ffffff") - # 蓝色渐变 header - for row in range(HEADER_H): - ratio = row / HEADER_H - r = int(61 - (61 - 42) * ratio) # 61 -> 42 - g = int(133 - (133 - 98) * ratio) # 133 -> 98 - b = int(255 - (255 - 239) * ratio) # 255 -> 239 - draw.line([(0, row), (W, row)], fill=(r, g, b)) - - # LC 图标 - 已删除方框 - - date_text = data["date"] - date_bbox = draw.textbbox((0, 0), date_text, font=font_small) - date_w = date_bbox[2] - date_bbox[0] - draw.text( - (W - PAD - date_w, (HEADER_H - 18) // 2), - date_text, fill=(255, 255, 255), font=font_small, - ) - - cy = card_top + PAD - - diff_styles = { - "Easy": {"label": "Easy", "bg": (220, 252, 231), "fg": (22, 101, 52)}, - "Medium": {"label": "Medium", "bg": (254, 249, 195), "fg": (133, 77, 14)}, - "Hard": {"label": "Hard", "bg": (254, 202, 202), "fg": (153, 27, 27)}, - } - ds = diff_styles.get(data["difficulty"], {"label": data["difficulty"], "bg": (229, 231, 235), "fg": (55, 65, 81)}) - - for line in title_lines: - draw.text((PAD, cy), line, fill="#1a1a2e", font=font_title) - cy += 34 - cy += 8 - - meta_y = cy - id_text = f"#{data['question_id']}" - draw.text((PAD, meta_y + 4), id_text, fill="#9ca3af", font=font_small) - id_w = draw.textlength(id_text, font=font_small) + 16 - - badge_text = ds["label"] - badge_bbox = draw.textbbox((0, 0), badge_text, font=font_small) - badge_w = badge_bbox[2] - badge_bbox[0] + 24 - badge_h = 28 - badge_x = PAD + id_w - draw.rounded_rectangle( - [badge_x, meta_y, badge_x + badge_w, meta_y + badge_h], - radius=14, - fill=ds["bg"], - ) - draw.text((badge_x + 12, meta_y + 4), badge_text, fill=ds["fg"], font=font_small) - - rate_text = f"Accept: {data['ac_rate']:.1f}%" - rate_x = badge_x + badge_w + 16 - draw.text((rate_x, meta_y + 4), rate_text, fill="#059669", font=font_small) - - cy = meta_y + badge_h + 20 - - if data["tags"]: - tag_x = PAD - tag_y = cy - for t in data["tags"]: - tw = draw.textlength(t, font=font_tag) + 24 - if tag_x + tw > W - PAD: - tag_x = PAD - tag_y += 32 + 8 - draw.rounded_rectangle( - [tag_x, tag_y, tag_x + tw, tag_y + 30], - radius=8, - fill=(238, 242, 255), - ) - draw.text((tag_x + 12, tag_y + 6), t, fill=(67, 56, 202), font=font_tag) - tag_x += tw + 8 - cy = tag_y + 38 - - if desc_text: - draw.text((PAD, cy), "Description", fill="#64748b", font=font_small) - cy += 24 - - desc_lines = _wrap_text(desc_text, font_body, W - PAD * 2, draw) - for line in desc_lines: - if line.strip() == "": - cy += 8 - continue - draw.text((PAD, cy), line, fill="#374151", font=font_body) - cy += 24 - - Path(img_path).parent.mkdir(parents=True, exist_ok=True) - img.save(img_path, "PNG") - - -def render_markdown(data, desc_md=""): - """Convert LeetCode content to Markdown format. - - Args: - data: LeetCode question data dict - desc_md: Pre-converted markdown (optional). If not provided, will be generated from data["content"] - """ - if not desc_md and data["content"]: - desc_md = _html_to_markdown(data["content"]) - - diff_map = { - "Easy": "Easy", - "Medium": "Medium", - "Hard": "Hard", - } - difficulty = diff_map.get(data["difficulty"], data["difficulty"]) - - tags_md = "" - if data["tags"]: - tags_md = "\n".join([f"- `{tag}`" for tag in data["tags"]]) - - md = f"""# {data['title']} - -## Info -- **#{data['question_id']}** | {difficulty} | Accept Rate: {data['ac_rate']:.1f}% -- Date: {data['date']} -- Tags: -{tags_md} - -## Description -{desc_md} - -## Examples -``` -{data['example_cases']} -``` - -## Link -[data](https://leetcode.com/problems/{data['title_slug']}/) -""" - return md - - -# === Main === -def main(): - if len(sys.argv) < 2: - print("Usage: python leetcode_card.py ", file=sys.stderr) - sys.exit(1) - - output = sys.argv[1] - ext = Path(output).suffix.lower() - - data = fetch_daily_challenge() - desc_md = _html_to_markdown(data["content"]) if data["content"] else "" - - if ext == ".md": - md = render_markdown(data, desc_md) - Path(output).parent.mkdir(parents=True, exist_ok=True) - Path(output).write_text(md, encoding="utf-8") - else: - render_image(data, desc_md, output) - - print(json.dumps({"desc": desc_md, "output_file": output})) - - -if __name__ == "__main__": - main()