const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const source = fs.readFileSync('content/phplife-mail.js', 'utf8'); function extractFunction(name) { const markers = [`async function ${name}(`, `function ${name}(`]; const start = markers .map((marker) => source.indexOf(marker)) .find((index) => index >= 0); if (start < 0) { throw new Error(`missing function ${name}`); } let parenDepth = 0; let signatureEnded = false; let braceStart = -1; for (let i = start; i < source.length; i += 1) { const ch = source[i]; if (ch === '(') { parenDepth += 1; } else if (ch === ')') { parenDepth -= 1; if (parenDepth === 0) { signatureEnded = true; } } else if (ch === '{' && signatureEnded) { braceStart = i; break; } } if (braceStart < 0) { throw new Error(`missing body for function ${name}`); } let depth = 0; let end = braceStart; for (; end < source.length; end += 1) { const ch = source[end]; if (ch === '{') depth += 1; if (ch === '}') { depth -= 1; if (depth === 0) { end += 1; break; } } } return source.slice(start, end); } test('phplife mail parser extracts code and recipient from roundcube message view', () => { const bundle = [ extractFunction('normalizeText'), extractFunction('parseRoundcubeTimestamp'), extractFunction('extractVerificationCodes'), extractFunction('getMessageDetailsFromDocument'), ].join('\n'); const api = new Function(`${bundle} return { getMessageDetailsFromDocument, parseRoundcubeTimestamp, extractVerificationCodes }; `)(); const selectors = { 'h2.subject': { textContent: 'Your ChatGPT code is 866785' }, '.headers-table .header.from': { textContent: 'noreply@tm.openai.com' }, '.headers-table .header.to': { textContent: 'n2026041807@a4sky.com' }, '.headers-table .header.date': { textContent: '今天 12:30' }, '#messagebody': { innerText: 'Enter this temporary verification code to continue: 866785. ChatGPT Log-in Code 866785', textContent: 'Enter this temporary verification code to continue: 866785. ChatGPT Log-in Code 866785', }, }; const fakeDocument = { querySelector(selector) { return selectors[selector] || null; }, body: { innerText: '', textContent: '', }, }; const details = api.getMessageDetailsFromDocument(fakeDocument); assert.equal(details.subject, 'Your ChatGPT code is 866785'); assert.equal(details.from, 'noreply@tm.openai.com'); assert.equal(details.to, 'n2026041807@a4sky.com'); assert.equal(details.codes[0], '866785'); assert.equal(Number.isFinite(details.emailTimestamp), true); }); test('phplife mail parser can read verification code directly from list row title', () => { const bundle = [ extractFunction('normalizeText'), extractFunction('parseRoundcubeTimestamp'), extractFunction('extractVerificationCodes'), extractFunction('getRowText'), extractFunction('getRowDetails'), ].join('\n'); const api = new Function(`${bundle} return { getRowDetails }; `)(); const subjectNode = { getAttribute(name) { return name === 'title' ? 'Your ChatGPT code is 866785' : ''; }, textContent: 'fallback subject', }; const fromNode = { getAttribute() { return ''; }, textContent: 'noreply@tm.openai.com' }; const toNode = { getAttribute() { return ''; }, textContent: 'n2026041807@a4sky.com' }; const dateNode = { getAttribute() { return ''; }, textContent: '今天 12:30' }; const fakeRow = { querySelector(selector) { if (selector === 'td.subject') return subjectNode; if (selector === 'td.fromto') return fromNode; if (selector === 'td.to') return toNode; if (selector === 'td.date') return dateNode; return null; }, textContent: 'Your ChatGPT code is 866785 noreply@tm.openai.com n2026041807@a4sky.com 今天 12:30', }; const details = api.getRowDetails(fakeRow); assert.equal(details.subject, 'Your ChatGPT code is 866785'); assert.equal(details.codes[0], '866785'); });