const CheerioShim = { load: (html) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const $ = (selector, context) => { let elements; if (selector && selector.nodeType) { elements = [selector]; } else if (Array.isArray(selector)) { elements = selector; } else { const root = (context && context[0]) ? context[0] : (context || doc); elements = Array.from(root.querySelectorAll ? root.querySelectorAll(selector) : []); } return createWrapper(elements); }; function createWrapper(elements) { elements.forEach((el, i) => elements[i] = el); elements.attr = (name) => { if (elements.length === 0) return undefined; return elements[0].getAttribute ? elements[0].getAttribute(name) : null; }; elements.text = () => { return elements.map(el => el.textContent).join(''); }; elements.find = (selector) => { let found = []; elements.forEach(el => { if (el.querySelectorAll) { found = found.concat(Array.from(el.querySelectorAll(selector))); } }); return createWrapper(found); }; elements.each = (callback) => { elements.forEach((el, i) => { callback(i, el); }); return elements; }; elements.map = (callback) => { const results = elements.map((el, i) => callback(i, el)); return createWrapper(results.filter(r => r !== null && r !== undefined)); }; elements.get = () => Array.from(elements); elements.filter = (selectorOrFn) => { if (typeof selectorOrFn === 'function') { return createWrapper(elements.filter(selectorOrFn)); } return createWrapper(elements.filter(el => el.matches && el.matches(selectorOrFn))); }; elements.first = () => createWrapper(elements.length ? [elements[0]] : []); elements.last = () => createWrapper(elements.length ? [elements[elements.length - 1]] : []); elements.eq = (i) => createWrapper(elements.length > i ? [elements[i]] : []); return elements; } return $; } }; window.require = (moduleName) => { if (moduleName === 'cheerio') return CheerioShim; if (moduleName === 'node-fetch' || moduleName === 'fetch') return window.fetch.bind(window); return {}; }; window.module = { exports: {} }; class MockBrowser { constructor() { } async scrape(url, pageFn, options = {}) { emulatorLog(`[Browser] Scraping: ${url}`, 'info'); try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); return await this.executePageFn(pageFn, doc); } catch (error) { emulatorLog(`[Browser] Error: ${error.message}`, 'error'); if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) { emulatorLog('Tip: This looks like a CORS error. Ensure you have a "Allow CORS" extension enabled in your browser for testing.', 'warn'); } throw error; } } async executePageFn(pageFn, virtualDoc) { const fnString = pageFn.toString(); const wrapper = new Function("document", `return (${fnString})();`); return wrapper(virtualDoc); } } const codeInput = document.getElementById('code-input'); const runBtn = document.getElementById('run-btn'); const outputVisual = document.getElementById('visual-container'); const outputJson = document.getElementById('json-content'); const outputConsole = document.getElementById('console-content'); const originalConsoleLog = console.log; const originalConsoleError = console.error; const originalConsoleWarn = console.warn; function captureLogs() { console.log = (...args) => { originalConsoleLog(...args); emulatorLog(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '), 'info'); }; console.error = (...args) => { originalConsoleError(...args); emulatorLog(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '), 'error'); }; console.warn = (...args) => { originalConsoleWarn(...args); emulatorLog(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '), 'warn'); }; } function emulatorLog(msg, type = 'info') { const div = document.createElement('div'); div.className = `log-entry log-${type}`; div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; outputConsole.appendChild(div); outputConsole.scrollTop = outputConsole.scrollHeight; } window.switchTab = (tabName) => { ['visual', 'json', 'console'].forEach(t => { document.getElementById(`output-${t}`).classList.add('hidden'); }); document.getElementById(`output-${tabName}`).classList.remove('hidden'); document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); if (event && event.target) event.target.classList.add('active'); }; runBtn.addEventListener('click', async () => { outputVisual.innerHTML = ''; outputJson.textContent = ''; outputConsole.innerHTML = ''; window.module.exports = {}; captureLogs(); const code = codeInput.value; const functionName = document.getElementById('func-select').value; const argInput = document.getElementById('arg-input').value; const pageInput = parseInt(document.getElementById('page-input').value) || 1; if (!code.trim()) { alert("Please paste extension code first."); return; } emulatorLog("--- Starting Execution ---"); try { try { new Function(code)(); } catch (e) { throw new Error(`Syntax/Runtime Error in Extension Code: ${e.message}`); } const exports = window.module.exports; const keys = Object.keys(exports); if (keys.length === 0) throw new Error("No class found in module.exports. Did you add 'module.exports = { ClassName };'?"); const ExtensionClass = exports[keys[0]]; emulatorLog(`Loaded Class: ${keys[0]}`, 'info'); const browserInstance = new MockBrowser(); const extension = new ExtensionClass('node-fetch', 'cheerio', browserInstance); if (typeof extension[functionName] !== 'function') { throw new Error(`Function '${functionName}' not found in class '${keys[0]}'.`); } emulatorLog(`Calling ${functionName}('${argInput}', ${pageInput})...`); document.querySelector('.loading-state').classList.remove('hidden'); let result; if (functionName === 'fetchSearchResult') { result = await extension.fetchSearchResult(argInput, pageInput); } else if (functionName === 'fetchInfo') { result = await extension.fetchInfo(argInput); } else if (functionName === 'findChapters') { result = await extension.findChapters(argInput); } else if (functionName === 'findChapterPages') { result = await extension.findChapterPages(argInput); } document.querySelector('.loading-state').classList.add('hidden'); emulatorLog("Data received. Rendering...", 'info'); renderJson(result); renderVisuals(result, functionName); if (result) switchTab('visual'); } catch (err) { document.querySelector('.loading-state').classList.add('hidden'); emulatorLog(err.message, 'error'); console.error(err); } }); function renderJson(data) { outputJson.textContent = JSON.stringify(data, null, 2); } function renderVisuals(data, type) { outputVisual.innerHTML = ''; if (!data) { outputVisual.innerHTML = '
${JSON.stringify(data, null, 2)}`;
outputVisual.appendChild(container);
}
}