diff options
Diffstat (limited to 'html/immoral/bookmarklet.js')
-rw-r--r-- | html/immoral/bookmarklet.js | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/html/immoral/bookmarklet.js b/html/immoral/bookmarklet.js new file mode 100644 index 0000000..7f61ec4 --- /dev/null +++ b/html/immoral/bookmarklet.js @@ -0,0 +1,564 @@ +(function() { + // Prevent multiple instances from running at once + if (window.immoralFontVacuum) { + alert('Web Font Vacuum is already running!'); + return; + } + window.immoralFontVacuum = true; + + const logCollector = { + logs: [], + group: function(label) { + this.logs.push(`\n### ${label}`); + }, + groupEnd: function() { + this.logs.push(`### End Group\n`); + }, + log: function(...args) { + this.logs.push(args.map(arg => + typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg + ).join(' ')); + }, + warn: function(...args) { + this.logs.push(`⚠️ ${args.map(arg => + typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg + ).join(' ')}`); + }, + error: function(...args) { + this.logs.push(`❌ ${args.map(arg => + typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg + ).join(' ')}`); + }, + getReport: function() { + return `Font Vacuum Report +================== +Time: ${new Date().toISOString()} +URL: ${window.location.href} + +${this.logs.join('\n')}`; + } + }; + + const styleRoot = document.createElement('div'); + styleRoot.className = 'fv-root'; + styleRoot.style.all = 'initial'; // Reset all styles + + const style = document.createElement('style'); + style.textContent = ` + .fv-root { + font: 16px system-ui, -apple-system, sans-serif; + color: #333333; + line-height: 1.4; + box-sizing: border-box; + } + + .fv-root * { + box-sizing: inherit; + font-family: inherit; + line-height: inherit; + color: inherit; + } + + .fv-container { + position: fixed; + top: 20px; + right: 20px; + width: 400px; + max-height: 90vh; + background: #f5f5f5; + z-index: 999999; + overflow-y: auto; + display: flex; + flex-direction: column; + border: 3px solid #333; + box-shadow: 8px 8px 0 #ff4d00; + } + + .fv-header { + padding: 1rem; + background: #333333; + color: #f5f5f5; + display: flex; + justify-content: space-between; + align-items: center; + cursor: move; + user-select: none; + flex-shrink: 0; + } + + .fv-header h1 { + margin: 0; + font-size: 1.1rem; + line-height: 1; + } + + .fv-close { + background: none; + border: none; + color: #f5f5f5; + cursor: pointer; + font-size: 1.5rem; + padding: 0; + margin: 0; + line-height: 1; + display: flex; + align-items: center; + } + + .fv-content { + padding: 1rem; + overflow-y: auto; + flex-grow: 1; + } + + .fv-footer { + padding: 0.75rem 1rem; + background: #333333; + color: #f5f5f5; + display: flex; + justify-content: flex-end; + align-items: center; + flex-shrink: 0; + } + + .fv-footer-button { + background: #555; + color: #f5f5f5; + border: none; + padding: 0.5rem 1rem; + cursor: pointer; + font-size: 0.9em; + display: flex; + align-items: center; + gap: 0.5rem; + } + + .fv-footer-button:hover { + background: #666; + } + + .fv-font-item { + margin-bottom: 1rem; + padding: 1rem; + border: 1px solid #ddd; + background: #ffffff; + } + + .fv-font-item h3 { + margin: 0 0 1rem 0; + padding: 0; + font-size: 1.1em; + font-weight: 600; + } + + .fv-preview { + margin: 1rem 0; + padding: 1rem; + border: 1px dashed #333; + background: #ffffff; + } + + .fv-button { + background: #333; + color: #f5f5f5; + border: none; + padding: 0.5rem 1rem; + cursor: pointer; + margin: 0.25rem 0.5rem 0.25rem 0; + font-size: 0.9em; + } + + .fv-button:last-child { + margin-right: 0; + } + + .fv-button:hover { + background: #444; + } + `; + styleRoot.appendChild(style); + document.body.appendChild(styleRoot); + + const container = document.createElement('div'); + container.className = 'fv-container'; + styleRoot.appendChild(container); + + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + const header = document.createElement('header'); + header.className = 'fv-header'; + header.innerHTML = ` + <h1>Web Font Vacuum</h1> + <button class="fv-close">×</button> + `; + + header.addEventListener('mousedown', dragStart); + document.addEventListener('mousemove', drag); + document.addEventListener('mouseup', dragEnd); + + function dragStart(e) { + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + if (e.target === header) { + isDragging = true; + } + } + + function drag(e) { + if (isDragging) { + e.preventDefault(); + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + xOffset = currentX; + yOffset = currentY; + container.style.transform = `translate(${currentX}px, ${currentY}px)`; + } + } + + function dragEnd() { + isDragging = false; + } + + header.querySelector('.fv-close').addEventListener('click', () => { + document.body.removeChild(styleRoot); + window.immoralFontVacuum = false; + }); + + const content = document.createElement('div'); + content.className = 'fv-content'; + + function extractFontUrls(cssText, baseUrl) { + const fontUrls = []; + const fontFaceRegex = /@font-face\s*{[^}]*}/g; + const urlRegex = /url\(['"]?([^'"\)]+)['"]?\)/g; + const fontFamilyRegex = /font-family\s*:\s*['"]?([^'";]*)['"]?/; + + function resolveUrl(url, base) { + try { + // Protocol-relative URLs + if (url.startsWith('//')) { + return `${location.protocol}${url}`; + } + // Absolute URLs + if (url.match(/^https?:\/\//)) { + return url; + } + // Root-relative URLs + if (url.startsWith('/')) { + return `${location.origin}${url}`; + } + // Relative URLs - use stylesheet URL as base if available + return new URL(url, base || location.href).href; + } catch (e) { + console.warn('Failed to resolve URL:', url, e); + return url; + } + } + + let fontFaceMatch; + while ((fontFaceMatch = fontFaceRegex.exec(cssText)) !== null) { + const fontFaceBlock = fontFaceMatch[0]; + const familyMatch = fontFaceBlock.match(fontFamilyRegex); + const fontFamily = familyMatch ? familyMatch[1].trim() : 'Unknown Font'; + + let urlMatch; + while ((urlMatch = urlRegex.exec(fontFaceBlock)) !== null) { + let fontUrl = urlMatch[1].trim(); + + // Skip data: URLs + if (fontUrl.startsWith('data:')) continue; + + // Only process known font file types + if (!fontUrl.match(/\.(woff2?|ttf|otf|eot)(\?.*)?$/i)) continue; + + // Resolve the URL relative to the stylesheet's URL + fontUrl = resolveUrl(fontUrl, baseUrl); + + fontUrls.push({ + family: fontFamily, + url: fontUrl, + filename: fontUrl.split('/').pop().split('?')[0], + cssRule: fontFaceBlock + }); + } + } + return fontUrls; + } + + function findFonts() { + const fonts = new Map(); + logCollector.group('Font Vacuum: Scanning Stylesheets'); + + logCollector.log(`Found ${document.styleSheets.length} stylesheets`); + + for (const sheet of document.styleSheets) { + try { + const baseUrl = sheet.href; + logCollector.group(`Stylesheet: ${baseUrl || 'inline'}`); + const cssRules = sheet.cssRules || sheet.rules; + logCollector.log(`- Rules found: ${cssRules.length}`); + + let cssText = ''; + let fontFaceCount = 0; + for (const rule of cssRules) { + if (rule.constructor.name === 'CSSFontFaceRule') { + fontFaceCount++; + } + cssText += rule.cssText + '\n'; + } + logCollector.log(`- @font-face rules found: ${fontFaceCount}`); + + const fontUrls = extractFontUrls(cssText, baseUrl); + logCollector.log(`- Font URLs extracted: ${fontUrls.length}`); + fontUrls.forEach(font => { + logCollector.log(` • ${font.family}: ${font.url}`); + if (!fonts.has(font.family)) { + fonts.set(font.family, { + variants: [], + cssRule: font.cssRule + }); + } + fonts.get(font.family).variants.push(font); + }); + logCollector.groupEnd(); + } catch (e) { + logCollector.warn(`Could not access stylesheet:`, sheet.href, e); + logCollector.groupEnd(); + } + } + + const results = Array.from(fonts.entries()).map(([family, data]) => ({ + family, + variants: data.variants, + cssRule: data.cssRule + })); + + logCollector.log('Final Results:', { + totalFamilies: results.length, + families: results.map(f => ({ + family: f.family, + variants: f.variants.length, + urls: f.variants.map(v => v.url) + })) + }); + logCollector.groupEnd(); + + return results; + } + + async function downloadFont(url, filename) { + try { + logCollector.group(`Font Vacuum: Downloading ${filename} from ${url}`); + + logCollector.log('Searching for existing font-face rule...'); + const existingFontRule = Array.from(document.styleSheets) + .flatMap(sheet => { + try { + return Array.from(sheet.cssRules); + } catch (e) { + return []; + } + }) + .find(rule => + rule.constructor.name === 'CSSFontFaceRule' && + rule.cssText.includes(url) + ); + + logCollector.log('Existing font-face rule found:', !!existingFontRule); + let response; + + if (existingFontRule) { + logCollector.log('Attempting to fetch using existing rule credentials...'); + const fontBlob = await fetch(url, { + mode: 'cors', + credentials: 'include', + headers: { + 'Origin': window.location.origin + } + }).then(r => r.blob()); + response = new Response(fontBlob); + } else { + logCollector.log('No existing rule found, attempting direct fetch...'); + response = await fetch(url, { + mode: 'cors', + credentials: 'include', + headers: { + 'Origin': window.location.origin + } + }); + } + + if (!response.ok) { + throw new Error(`Network response was not ok. Status: ${response.status}`); + } + + logCollector.log('Font fetched successfully, preparing download...'); + const blob = await response.blob(); + logCollector.log('Font blob size:', blob.size, 'bytes'); + + const objectUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = objectUrl; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => URL.revokeObjectURL(objectUrl), 100); + logCollector.log('Download initiated successfully'); + logCollector.groupEnd(); + return true; + } catch (error) { + logCollector.error('Error downloading font:', error); + logCollector.groupEnd(); + alert(`Error downloading font: ${error.message}\n\nTroubleshooting tips:\n1. Check the console for detailed logs\n2. Try using your browser's developer tools Network tab to find and download the font file directly\n3. Some sites may block direct font downloads`); + return false; + } + } + + async function previewFont(url, fontFamily) { + try { + logCollector.group(`Font Vacuum: Previewing ${fontFamily} from ${url}`); + + const existingFontRule = Array.from(document.styleSheets) + .flatMap(sheet => { + try { + return Array.from(sheet.cssRules); + } catch (e) { + return []; + } + }) + .find(rule => + rule.constructor.name === 'CSSFontFaceRule' && + rule.cssText.includes(url) + ); + + if (existingFontRule) { + logCollector.log('Using existing font-face rule for preview'); + logCollector.groupEnd(); + return true; + } + + logCollector.log('No existing rule found, attempting to load font...'); + const response = await fetch(url, { + mode: 'cors', + credentials: 'include', + headers: { + 'Origin': window.location.origin + } + }); + + if (!response.ok) { + throw new Error(`Network response was not ok. Status: ${response.status}`); + } + + const blob = await response.blob(); + logCollector.log('Font blob size:', blob.size, 'bytes'); + const fontUrl = URL.createObjectURL(blob); + + const fontFace = new FontFace(fontFamily, `url(${fontUrl})`, { + style: 'normal', + weight: '400', + display: 'swap' + }); + + const loadedFont = await fontFace.load(); + document.fonts.add(loadedFont); + URL.revokeObjectURL(fontUrl); + + logCollector.log('Font loaded successfully'); + logCollector.groupEnd(); + return true; + } catch (error) { + logCollector.error('Error loading font:', error); + logCollector.groupEnd(); + return false; + } + } + + const fonts = findFonts(); + + if (fonts.length === 0) { + content.innerHTML = '<p>No web fonts found on this page.</p>'; + } else { + fonts.forEach(fontData => { + const fontItem = document.createElement('div'); + fontItem.className = 'fv-font-item'; + + const fontName = document.createElement('h3'); + fontName.style.margin = '0 0 1rem 0'; + fontName.textContent = fontData.family; + fontItem.appendChild(fontName); + + const preview = document.createElement('div'); + preview.className = 'fv-preview'; + preview.innerHTML = '0123456789<br><br>Society for me my misery<br>Since Gift of Thee --<br><br>The quick brown fox jumps over the lazy dog!?'; + fontItem.appendChild(preview); + + const uniqueDownloads = new Map(); + fontData.variants.forEach(variant => { + if (!uniqueDownloads.has(variant.url)) { + uniqueDownloads.set(variant.url, { + filename: variant.filename, + url: variant.url + }); + } + }); + + const buttonContainer = document.createElement('div'); + buttonContainer.style.marginTop = '1rem'; + + uniqueDownloads.forEach(({filename, url}) => { + const downloadBtn = document.createElement('button'); + downloadBtn.className = 'fv-button'; + downloadBtn.textContent = `⬇ Download ${filename}`; + downloadBtn.addEventListener('click', () => downloadFont(url, filename)); + buttonContainer.appendChild(downloadBtn); + }); + + fontItem.appendChild(buttonContainer); + + fontData.variants.forEach(async (variant) => { + if (await previewFont(variant.url, fontData.family)) { + preview.style.fontFamily = fontData.family; + if (variant.cssRule) { + const fontStyle = variant.cssRule.match(/font-style:\s*([^;]+)/); + const fontWeight = variant.cssRule.match(/font-weight:\s*([^;]+)/); + if (fontStyle) preview.style.fontStyle = fontStyle[1]; + if (fontWeight) preview.style.fontWeight = fontWeight[1]; + } + } + }); + + content.appendChild(fontItem); + }); + } + + const footer = document.createElement('div'); + footer.className = 'fv-footer'; + + const reportBtn = document.createElement('button'); + reportBtn.className = 'fv-footer-button'; + reportBtn.innerHTML = '<span>📋</span><span>Copy Debug Report</span>'; + reportBtn.addEventListener('click', () => { + const report = logCollector.getReport(); + navigator.clipboard.writeText(report).then(() => { + reportBtn.innerHTML = '<span>✅</span><span>Report Copied!</span>'; + setTimeout(() => { + reportBtn.innerHTML = '<span>📋</span><span>Copy Debug Report</span>'; + }, 2000); + }); + }); + + footer.appendChild(reportBtn); + container.appendChild(header); + container.appendChild(content); + container.appendChild(footer); + styleRoot.appendChild(container); +})(); \ No newline at end of file |