/** * 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); });