UNPKG

chromancer

Version:

A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.

123 lines (122 loc) 5.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DOMDigestCollector = void 0; class DOMDigestCollector { page; static REJECTED_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG', 'IMG', 'HEAD']; static MAX_PATTERNS = 30; static MAX_TEXTS = 30; static MAX_ATTRS = 30; static MAX_TEXT_LENGTH = 100; static MIN_PATTERN_COUNT = 3; constructor(page) { this.page = page; } async collect() { return await this.page.evaluate(() => { const digestKey = '__chromancerDigest'; const cached = window[digestKey]; if (cached && cached.url === window.location.href) { return cached; } const rejected = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG', 'IMG', 'HEAD']; const elements = Array.from(document.querySelectorAll('*')) .filter(el => !rejected.includes(el.tagName)); // Collect patterns const classPatterns = new Map(); const rolePatterns = new Map(); const tagPatterns = new Map(); elements.forEach(el => { // Count class patterns if (el.className && typeof el.className === 'string') { el.className.split(/\s+/).filter(c => c).forEach(cls => { const selector = `.${cls}`; classPatterns.set(selector, (classPatterns.get(selector) || 0) + 1); }); } // Count role patterns const role = el.getAttribute('role'); if (role) { const selector = `[role="${role}"]`; rolePatterns.set(selector, (rolePatterns.get(selector) || 0) + 1); } // Count tag patterns for meaningful tags const meaningfulTags = ['article', 'section', 'aside', 'nav', 'header', 'footer', 'main']; if (meaningfulTags.includes(el.tagName.toLowerCase())) { tagPatterns.set(el.tagName.toLowerCase(), (tagPatterns.get(el.tagName.toLowerCase()) || 0) + 1); } }); // Combine and sort patterns const allPatterns = [ ...Array.from(classPatterns.entries()), ...Array.from(rolePatterns.entries()), ...Array.from(tagPatterns.entries()) ] .filter(([_, count]) => count >= 3) .sort((a, b) => b[1] - a[1]) .slice(0, 30) .map(([selector, count]) => ({ selector, count })); // Collect meaningful text nodes const texts = []; const textNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { const parent = node.parentElement; if (!parent || rejected.includes(parent.tagName)) { return NodeFilter.FILTER_REJECT; } const text = node.textContent?.trim() || ''; if (text.length > 10 && text.length < 100) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } }); let textNode; while ((textNode = textNodes.nextNode()) && texts.length < 30) { const text = textNode.textContent?.trim() || ''; if (text && !texts.includes(text)) { texts.push(text); } } // Collect data attributes const attrs = new Set(); elements.forEach(el => { Array.from(el.attributes).forEach(attr => { if (attr.name.startsWith('data-') || attr.name.startsWith('aria-')) { attrs.add(attr.name); } }); }); const digest = { url: window.location.href, title: document.title, patterns: allPatterns, texts: texts.slice(0, 30), attrs: Array.from(attrs).slice(0, 30), timestamp: Date.now() }; window[digestKey] = digest; return digest; }); } async getDigestSize(digest) { return JSON.stringify(digest).length; } formatForClaude(digest) { const lines = [ `URL: ${digest.url}`, `Title: ${digest.title}`, '', 'Top Patterns:', ...digest.patterns.slice(0, 10).map(p => ` ${p.selector} (${p.count} elements)`), '', 'Sample Text Content:', ...digest.texts.slice(0, 10).map(t => ` "${t}"`), '', 'Available Attributes:', ` ${digest.attrs.slice(0, 20).join(', ')}` ]; return lines.join('\n'); } } exports.DOMDigestCollector = DOMDigestCollector;