diff options
author | elioat <elioat@tilde.institute> | 2025-03-15 20:14:11 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-15 20:14:11 -0400 |
commit | 1def3741cfb8c08e2cd276306c8829408f201f69 (patch) | |
tree | a5152a6b3a27c655bea52d610ab58fa6b0f3d87c | |
parent | a159ef43f6a0d3bb8b25a9a12d0f514848bc99da (diff) | |
download | tour-1def3741cfb8c08e2cd276306c8829408f201f69.tar.gz |
*
-rw-r--r-- | html/web-font-vacuum/app.js | 501 | ||||
-rw-r--r-- | html/web-font-vacuum/index.html | 75 |
2 files changed, 576 insertions, 0 deletions
diff --git a/html/web-font-vacuum/app.js b/html/web-font-vacuum/app.js new file mode 100644 index 0000000..445f515 --- /dev/null +++ b/html/web-font-vacuum/app.js @@ -0,0 +1,501 @@ +// List of CORS proxies to try +const CORS_PROXIES = [ + { + name: 'allorigins', + url: 'https://api.allorigins.win/raw?url=', + urlFormatter: url => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}` + }, + { + name: 'corsproxy.io', + url: 'https://corsproxy.io/?', + urlFormatter: url => `https://corsproxy.io/?${encodeURIComponent(url)}` + }, + { + name: 'cors.sh', + url: 'https://cors.sh/', + urlFormatter: url => `https://cors.sh/${url}` + }, + { + name: 'corsanywhere', + url: 'https://cors-anywhere.herokuapp.com/', + urlFormatter: url => `https://cors-anywhere.herokuapp.com/${url}` + }, + { + name: 'thingproxy', + url: 'https://thingproxy.freeboard.io/fetch/', + urlFormatter: url => `https://thingproxy.freeboard.io/fetch/${url}` + } +]; + +// Keep track of which proxy worked last +let lastWorkingProxyIndex = 0; + +// Helper function to try multiple proxies +async function fetchWithProxies(url, attempt = 0, isBinary = false) { + // Start with the last working proxy + const startIndex = lastWorkingProxyIndex; + + for (let i = 0; i < CORS_PROXIES.length; i++) { + // Calculate the current proxy index, wrapping around if necessary + const proxyIndex = (startIndex + i) % CORS_PROXIES.length; + const proxy = CORS_PROXIES[proxyIndex]; + + try { + console.log(`Trying proxy: ${proxy.name} for URL: ${url}`); + + const fetchOptions = { + headers: { + 'Accept': isBinary ? '*/*' : 'text/html,application/xhtml+xml,text/css' + } + }; + + // For binary data, set responseType to arraybuffer if supported + if (isBinary) { + fetchOptions.mode = 'cors'; + } + + const response = await fetch(proxy.urlFormatter(url), fetchOptions); + + if (response.ok) { + // Remember this working proxy for next time + lastWorkingProxyIndex = proxyIndex; + return response; + } + } catch (error) { + console.log(`Proxy ${proxy.name} failed:`, error); + // Continue to next proxy + } + } + + // If we get here, all proxies failed + throw new Error('All proxies failed to fetch the resource'); +} + +// Function to handle font downloading +async function downloadFont(url, filename) { + try { + console.log('Downloading font from:', url); + const response = await fetchWithProxies(url, 0, true); + + const arrayBuffer = await response.arrayBuffer(); + if (arrayBuffer.byteLength === 0) { + throw new Error('Received empty font file'); + } + + // Convert ArrayBuffer to Blob with proper MIME type + const blob = new Blob([arrayBuffer], { type: getFontMimeType(url) }); + + // Create a temporary link to trigger the download + const objectUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = objectUrl; + link.download = filename; + + // Append to body, click, and remove + document.body.appendChild(link); + link.click(); + + // Small delay to ensure download starts before cleanup + setTimeout(() => { + document.body.removeChild(link); + URL.revokeObjectURL(objectUrl); + }, 100); + + return true; + } catch (error) { + console.error('Error downloading font:', error); + alert(`Error downloading font: ${error.message}`); + return false; + } +} + +// Helper function to determine MIME type based on font URL +function getFontMimeType(url) { + const extension = url.split('.').pop().toLowerCase().split('?')[0]; + switch (extension) { + case 'woff': + return 'font/woff'; + case 'woff2': + return 'font/woff2'; + case 'ttf': + return 'font/ttf'; + case 'otf': + return 'font/otf'; + default: + return 'application/octet-stream'; + } +} + +document.addEventListener('DOMContentLoaded', () => { + const urlInput = document.getElementById('urlInput'); + const analyzeBtn = document.getElementById('analyzeBtn'); + const resultsDiv = document.getElementById('results'); + const errorDiv = document.getElementById('error'); + + // Sample text for font preview + const PREVIEW_TEXT = 'The quick brown fox jumps over the lazy dog 0123456789'; + + // Function to load and preview a font + async function previewFont(url, fontFamily) { + try { + console.log('Loading font from:', url); + const response = await fetchWithProxies(url, 0, true); + + const arrayBuffer = await response.arrayBuffer(); + if (arrayBuffer.byteLength === 0) { + throw new Error('Received empty font file'); + } + + // Convert ArrayBuffer to Blob + const blob = new Blob([arrayBuffer], { type: getFontMimeType(url) }); + + // Try using a data URL instead of a blob URL + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onload = async function() { + try { + const dataUrl = reader.result; + + // Create and load the font + const fontFace = new FontFace(fontFamily, `url(${dataUrl})`, { + style: 'normal', + weight: '400', + display: 'swap' + }); + + // Load the font into the document + const loadedFont = await fontFace.load(); + document.fonts.add(loadedFont); + + resolve(true); + } catch (loadError) { + console.error('Font load error:', loadError); + + // Try fallback method with blob URL + try { + 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); + resolve(true); + } catch (fallbackError) { + console.error('Font fallback load error:', fallbackError); + reject(fallbackError); + } + } + }; + + reader.onerror = function() { + reject(new Error('Failed to read font file')); + }; + + reader.readAsDataURL(blob); + }); + } catch (error) { + console.error('Error loading font for preview:', error); + return false; + } + } + + analyzeBtn.addEventListener('click', async () => { + const url = urlInput.value.trim(); + if (!url) { + showError('Please enter a valid URL'); + return; + } + + try { + // Clear previous results and errors + resultsDiv.innerHTML = ''; + errorDiv.style.display = 'none'; + + // Show loading state + resultsDiv.innerHTML = '<p>Analyzing webpage, please wait...</p>'; + + // Fetch the target webpage through the proxy system + const response = await fetchWithProxies(url); + const html = await response.text(); + + // Create a temporary DOM to parse the HTML + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + + // Find all potential font sources + const fontUrls = new Set(); + + // Get the base URL (domain) + const baseUrlObj = new URL(url); + const domain = baseUrlObj.origin; + + // Check common font directories + const commonFontPaths = [ + '/assets/fonts/', + '/fonts/', + '/assets/', + '/css/fonts/', + '/wp-content/themes/*/fonts/', + '/static/fonts/' + ]; + + console.log('Checking common font paths for:', domain); + + // Check for direct font links in the HTML + extractDirectFontLinks(doc, url, fontUrls); + + // Check stylesheet links + const cssPromises = Array.from(doc.querySelectorAll('link[rel="stylesheet"]')).map(link => { + const href = link.getAttribute('href'); + if (!href) return Promise.resolve(); + + const cssUrl = new URL(href, url).href; + console.log('Processing CSS URL:', cssUrl); + return processCssUrl(cssUrl, fontUrls, url); + }); + + // Wait for all CSS files to be processed + await Promise.all(cssPromises.filter(Boolean)); + + // Check style tags + doc.querySelectorAll('style').forEach(style => { + extractFontUrlsFromCss(style.textContent, url, fontUrls); + }); + + // If no fonts found, try checking common paths + if (fontUrls.size === 0) { + console.log('No fonts found in CSS, checking common paths...'); + + const commonPathPromises = commonFontPaths.map(async path => { + try { + // Try to access the directory + const directoryUrl = new URL(path, domain).href; + console.log('Checking directory:', directoryUrl); + + // We can't list directory contents directly, but we can try common font names + const commonFontNames = ['font', 'fonts', 'webfont', 'custom-font', 'main']; + const fontExtensions = ['woff', 'woff2', 'ttf', 'otf']; + + for (const name of commonFontNames) { + for (const ext of fontExtensions) { + const fontUrl = `${directoryUrl}${name}.${ext}`; + try { + const fontResponse = await fetchWithProxies(fontUrl, 0, true); + if (fontResponse.ok) { + console.log('Found font at common path:', fontUrl); + fontUrls.add(fontUrl); + } + } catch (error) { + // Ignore errors for common path checks + } + } + } + } catch (error) { + // Ignore errors for common path checks + } + }); + + await Promise.all(commonPathPromises); + } + + // Display results + if (fontUrls.size === 0) { + resultsDiv.innerHTML = '<p>No web fonts (WOFF/TTF/WOFF2/OTF) were found on this page.</p>'; + } else { + displayFontUrls(Array.from(fontUrls)); + } + + } catch (error) { + showError(`Error analyzing the webpage: ${error.message}`); + console.error('Full error:', error); + } + }); + + async function processCssUrl(cssUrl, fontUrls, baseUrl) { + try { + console.log('Fetching CSS from:', cssUrl); + const response = await fetchWithProxies(cssUrl); + const css = await response.text(); + + // Use the CSS URL as the base URL for resolving font URLs + extractFontUrlsFromCss(css, cssUrl, fontUrls); + } catch (error) { + console.error(`Error processing CSS from ${cssUrl}:`, error); + } + } + + function extractFontUrlsFromCss(css, baseUrl, fontUrls) { + const fontFaceRegex = /@font-face\s*{[^}]*}/g; + const urlRegex = /url\(['"]?([^'")]+)['"]?\)/g; + + const fontFaces = css.match(fontFaceRegex) || []; + + fontFaces.forEach(fontFace => { + let match; + while ((match = urlRegex.exec(fontFace)) !== null) { + let fontUrl = match[1]; + fontUrl = fontUrl.replace(/['"]/g, ''); + + if (fontUrl.match(/\.(woff|woff2|ttf|otf)(\?.*)?$/i)) { + try { + // Properly resolve the font URL against the CSS URL (baseUrl) + const absoluteUrl = new URL(fontUrl, baseUrl).href; + console.log('Found font URL:', absoluteUrl); + fontUrls.add(absoluteUrl); + } catch (error) { + console.error('Error resolving font URL:', fontUrl, error); + } + } + } + }); + } + + // Function to check for direct font links in the HTML + function extractDirectFontLinks(doc, baseUrl, fontUrls) { + // Check for preload links + doc.querySelectorAll('link[rel="preload"][as="font"]').forEach(link => { + const href = link.getAttribute('href'); + if (href && href.match(/\.(woff|woff2|ttf|otf)(\?.*)?$/i)) { + try { + const absoluteUrl = new URL(href, baseUrl).href; + console.log('Found preloaded font:', absoluteUrl); + fontUrls.add(absoluteUrl); + } catch (error) { + console.error('Error resolving font URL:', href, error); + } + } + }); + + // Check for any links that might be fonts + doc.querySelectorAll('a[href]').forEach(link => { + const href = link.getAttribute('href'); + if (href && href.match(/\.(woff|woff2|ttf|otf)(\?.*)?$/i)) { + try { + const absoluteUrl = new URL(href, baseUrl).href; + console.log('Found linked font:', absoluteUrl); + fontUrls.add(absoluteUrl); + } catch (error) { + console.error('Error resolving font URL:', href, error); + } + } + }); + } + + async function displayFontUrls(urls) { + resultsDiv.innerHTML = '<h2>Found Font Files:</h2>'; + + // Create a container for all font items + const fontsContainer = document.createElement('div'); + fontsContainer.className = 'fonts-container'; + + for (const url of urls) { + const fontItem = document.createElement('div'); + fontItem.className = 'font-item'; + + const fontName = url.split('/').pop().split('?')[0]; + const extension = fontName.split('.').pop().toUpperCase(); + const fontFamily = `preview-${Math.random().toString(36).substr(2, 9)}`; + + // Create the font item HTML structure + fontItem.innerHTML = ` + <div class="font-info"> + <strong>${fontName}</strong> + <br> + <small>${extension} Font</small> + </div> + <div class="font-preview" style="display: none;"> + <div class="preview-text" style="font-family: '${fontFamily}', sans-serif;"> + ${PREVIEW_TEXT} + </div> + <div class="preview-sizes"> + <div style="font-size: 12px;">${PREVIEW_TEXT}</div> + <div style="font-size: 18px;">${PREVIEW_TEXT}</div> + <div style="font-size: 24px;">${PREVIEW_TEXT}</div> + </div> + </div> + <div class="font-actions"> + <button onclick="downloadFont('${url}', '${fontName}')">Download</button> + <button class="preview-btn">Preview</button> + </div> + `; + + // Add the font item to the container + fontsContainer.appendChild(fontItem); + + // Add preview button functionality + const previewBtn = fontItem.querySelector('.preview-btn'); + const previewDiv = fontItem.querySelector('.font-preview'); + let fontLoaded = false; + + previewBtn.addEventListener('click', async () => { + if (!fontLoaded) { + previewBtn.textContent = 'Loading...'; + fontLoaded = await previewFont(url, fontFamily); + previewBtn.textContent = 'Preview'; + } + + if (fontLoaded) { + const isShowing = previewDiv.style.display !== 'none'; + previewDiv.style.display = isShowing ? 'none' : 'block'; + previewBtn.textContent = isShowing ? 'Preview' : 'Hide Preview'; + } else { + showError('Failed to load font for preview'); + } + }); + } + + // Add the container to the results + resultsDiv.appendChild(fontsContainer); + + // Add CSS for the new preview features + const style = document.createElement('style'); + style.textContent = ` + .fonts-container { + display: grid; + gap: 1rem; + } + .font-item { + background: white; + padding: 1rem; + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .font-info { + margin-bottom: 1rem; + } + .font-preview { + margin: 1rem 0; + padding: 1rem; + background: #f8f9fa; + border-radius: 4px; + } + .preview-sizes { + margin-top: 1rem; + display: grid; + gap: 0.5rem; + } + .font-actions { + display: flex; + gap: 0.5rem; + } + .preview-btn { + background: #6c757d; + } + .preview-btn:hover { + background: #5a6268; + } + `; + document.head.appendChild(style); + } + + function showError(message) { + errorDiv.textContent = message; + errorDiv.style.display = 'block'; + } +}); \ No newline at end of file diff --git a/html/web-font-vacuum/index.html b/html/web-font-vacuum/index.html new file mode 100644 index 0000000..8c4aad1 --- /dev/null +++ b/html/web-font-vacuum/index.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Web Font Vacuum</title> + <style> + body { + font-family: system-ui, -apple-system, sans-serif; + max-width: 800px; + margin: 2rem auto; + padding: 0 1rem; + line-height: 1.5; + background: #f0f2f5; + } + .container { + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .input-group { + display: flex; + gap: 1rem; + margin-bottom: 2rem; + } + input[type="url"] { + flex: 1; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ddd; + border-radius: 4px; + } + button { + padding: 0.75rem 1.5rem; + font-size: 1rem; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; + } + button:hover { + background: #0056b3; + } + .error { + color: #dc3545; + padding: 1rem; + background: #f8d7da; + border-radius: 4px; + margin-top: 1rem; + display: none; + } + #results { + margin-top: 1rem; + } + </style> +</head> +<body> + <div class="container"> + <h1>Web Font Vacuum</h1> + <p>Enter a URL to find and download web fonts (WOFF/TTF/WOFF2) from any website.</p> + + <div class="input-group"> + <input type="url" id="urlInput" placeholder="Enter website URL (e.g., https://example.com)" required> + <button id="analyzeBtn">Analyze Fonts</button> + </div> + + <div id="error" class="error"></div> + <div id="results"></div> + </div> + <script src="app.js"></script> +</body> +</html> |