URL EXTRACTOR - INTERACTIVE APP SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>URL Extractor</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--border-color: #475569;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-muted: #94a3b8;
--accent-blue: #3b82f6;
--accent-green: #10b981;
--accent-yellow: #f59e0b;
--accent-red: #ef4444;
}
:root.light-mode {
--bg-primary: #f8fafc;
--bg-secondary: #ffffff;
--bg-tertiary: #f1f5f9;
--border-color: #e2e8f0;
--text-primary: #0f172a;
--text-secondary: #334155;
--text-muted: #64748b;
--accent-blue: #3b82f6;
--accent-green: #10b981;
--accent-yellow: #f59e0b;
--accent-red: #ef4444;
}
html {
height: auto;
min-height: 100vh;
overflow-y: auto;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--bg-primary);
color: var(--text-secondary);
min-height: 100vh;
height: auto;
overflow-y: auto;
padding: 0;
margin: 0;
transition: background-color 0.3s ease, color 0.3s ease;
}
.app-wrapper {
min-height: 100vh;
height: auto;
padding: 20px;
background: var(--bg-primary);
transition: background-color 0.3s ease;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: var(--bg-secondary);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
height: auto;
min-height: 600px;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.light-mode .container {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
font-size: 28px;
color: #60a5fa;
}
.logo-text {
font-size: 22px;
font-weight: 600;
color: var(--text-primary);
}
.theme-toggle {
background: var(--border-color);
border: none;
color: var(--text-secondary);
padding: 10px 16px;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
transition: all 0.2s ease;
}
.theme-toggle:hover {
background: var(--text-muted);
}
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
height: auto;
min-height: 500px;
}
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
min-height: 800px;
}
}
.left-panel {
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
padding: 30px;
display: flex;
flex-direction: column;
height: auto;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.panel-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.panel-icon {
color: #60a5fa;
}
.url-input-wrapper {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
#url-input {
flex: 1;
padding: 14px 18px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--text-primary);
font-size: 15px;
outline: none;
transition: all 0.3s ease;
}
#url-input:focus {
border-color: var(--accent-blue);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
#fetch-button {
padding: 14px 28px;
background: var(--accent-blue);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
min-width: 160px;
}
#fetch-button:hover:not(:disabled) {
background: #2563eb;
transform: translateY(-1px);
}
#fetch-button:disabled {
background: #4b5563;
cursor: not-allowed;
}
#fetch-button.loading {
background: var(--accent-yellow);
position: relative;
padding-left: 50px;
}
#fetch-button.loading:before {
content: '';
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: translateY(-50%) rotate(360deg); }
}
.proxy-notice {
background: rgba(245, 158, 11, 0.1);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
color: #fbbf24;
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.light-mode .proxy-notice {
background: rgba(245, 158, 11, 0.05);
color: #92400e;
}
.proxy-notice a {
color: #60a5fa;
text-decoration: none;
font-weight: 500;
}
.proxy-notice a:hover {
text-decoration: underline;
}
.warning-icon {
color: var(--accent-yellow);
font-size: 18px;
}
.stats-container {
background: var(--bg-primary);
border-radius: 8px;
padding: 20px;
margin-top: 20px;
display: none;
transition: background-color 0.3s ease;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 20px;
}
@media (min-width: 768px) {
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.stat-card {
background: var(--bg-secondary);
border-radius: 8px;
padding: 16px;
border: 1px solid var(--border-color);
text-align: center;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #60a5fa;
margin-bottom: 6px;
}
.stat-label {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.url-types {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.url-type-tag {
background: var(--border-color);
color: var(--text-secondary);
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
transition: background-color 0.3s ease, color 0.3s ease;
}
.right-panel {
background: var(--bg-secondary);
display: flex;
flex-direction: column;
height: auto;
min-height: 500px;
transition: background-color 0.3s ease;
}
.results-header {
padding: 25px 30px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.results-header .panel-title {
margin-bottom: 0;
}
.filter-controls {
padding: 20px 30px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.filter-group {
display: flex;
align-items: center;
gap: 10px;
}
.filter-label {
font-size: 14px;
color: var(--text-muted);
white-space: nowrap;
}
.filter-input {
padding: 10px 14px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
font-size: 14px;
min-width: 200px;
width: 100%;
transition: all 0.3s ease;
}
.filter-select {
padding: 10px 14px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
color: var(--text-primary);
font-size: 14px;
min-width: 150px;
transition: all 0.3s ease;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox-label {
font-size: 14px;
color: var(--text-secondary);
}
input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--accent-blue);
}
.results-content {
flex: 1;
padding: 20px 30px;
background: var(--bg-primary);
overflow-y: auto;
max-height: 400px;
min-height: 300px;
transition: background-color 0.3s ease;
}
@media (min-height: 800px) {
.results-content {
max-height: 500px;
}
}
#results {
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-all;
}
.url-item {
padding: 12px 16px;
margin-bottom: 10px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.2s ease;
}
.url-item:hover {
border-color: var(--accent-blue);
background: rgba(59, 130, 246, 0.05);
}
.light-mode .url-item:hover {
background: rgba(59, 130, 246, 0.1);
}
.url-badge {
font-size: 11px;
padding: 4px 10px;
border-radius: 6px;
font-weight: 600;
min-width: 45px;
text-align: center;
flex-shrink: 0;
}
.badge-http { background: #1e40af; color: #dbeafe; }
.badge-https { background: #065f46; color: #d1fae5; }
.badge-image { background: #7c2d12; color: #fed7aa; }
.badge-css { background: #5b21b6; color: #ede9fe; }
.badge-js { background: #92400e; color: #fef3c7; }
.badge-internal { background: #374151; color: #d1d5db; }
.badge-external { background: #78350f; color: #fde68a; }
.light-mode .badge-internal { background: #d1d5db; color: #374151; }
.light-mode .badge-external { background: #fde68a; color: #78350f; }
.url-text {
flex: 1;
word-break: break-all;
color: var(--text-primary);
font-family: inherit;
}
.action-buttons {
padding: 20px 30px;
background: var(--bg-tertiary);
border-top: 1px solid var(--border-color);
display: flex;
flex-wrap: wrap;
gap: 10px;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.action-button {
padding: 12px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.2s ease;
flex: 1;
min-width: 120px;
}
@media (max-width: 768px) {
.action-buttons {
flex-direction: column;
}
.action-button {
min-width: 100%;
}
}
.action-button.primary {
background: var(--accent-blue);
color: white;
}
.action-button.primary:hover {
background: #2563eb;
}
.action-button.secondary {
background: var(--border-color);
color: var(--text-primary);
}
.action-button.secondary:hover {
background: var(--text-muted);
}
.action-button.warning {
background: var(--accent-yellow);
color: white;
}
.action-button.warning:hover {
background: #d97706;
}
.action-button.success {
background: var(--accent-green);
color: white;
}
.action-button.success:hover {
background: #059669;
}
.action-button.danger {
background: var(--accent-red);
color: white;
}
.action-button.danger:hover {
background: #dc2626;
}
.status-bar {
padding: 15px 30px;
background: var(--bg-tertiary);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: var(--text-muted);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.error-message {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #fca5a5;
padding: 12px 16px;
border-radius: 8px;
margin-top: 15px;
font-size: 14px;
display: none;
}
.light-mode .error-message {
color: #dc2626;
background: rgba(239, 68, 68, 0.05);
}
.error-message.visible {
display: block;
}
.success-message {
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
color: #a7f3d0;
padding: 12px 16px;
border-radius: 8px;
margin-top: 15px;
font-size: 14px;
display: none;
}
.light-mode .success-message {
color: #059669;
background: rgba(16, 185, 129, 0.05);
}
.success-message.visible {
display: block;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
@media (max-width: 768px) {
.app-wrapper {
padding: 10px;
}
.container {
border-radius: 8px;
}
.header {
padding: 15px 20px;
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.left-panel, .right-panel {
padding: 20px;
}
.url-input-wrapper {
flex-direction: column;
}
#fetch-button {
width: 100%;
}
.filter-controls {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.filter-group {
flex-direction: column;
align-items: stretch;
}
.filter-input, .filter-select {
min-width: 100%;
}
.results-header, .status-bar {
padding: 15px 20px;
}
}
body.in-iframe {
overflow-y: visible;
height: auto;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
transition: background-color 0.3s ease;
}
.light-mode .loading-overlay {
background: rgba(248, 250, 252, 0.8);
}
.loading-overlay.visible {
display: flex;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid var(--border-color);
border-top-color: var(--accent-blue);
border-radius: 50%;
animation: spin 1s linear infinite;
}
</style>
</head>
<body>
<div class="loading-overlay" id="loading-overlay">
<div class="spinner"></div>
</div>
<div class="app-wrapper">
<div class="container">
<div class="header">
<div class="logo">
<span class="logo-icon">🌐</span>
<span class="logo-text">URL Extractor</span>
</div>
<button class="theme-toggle" id="theme-toggle">
<span id="theme-icon">☀️</span>
<span id="theme-text">Light Mode</span>
</button>
</div>
<div class="main-content">
<div class="left-panel">
<div class="panel-title">
<span class="panel-icon">🔗</span>
<span>Webpage URL Extractor</span>
</div>
<div class="url-input-wrapper">
<input
type="url"
id="url-input"
placeholder="https://example.com"
autocomplete="off"
>
<button id="fetch-button">Fetch & Extract URLs</button>
</div>
<div class="proxy-notice">
<span class="warning-icon">⚠</span>
<span>
Using CORS proxy for cross-origin requests.
<a href="#" id="proxy-info">Learn more</a>
</span>
</div>
<div id="error-message" class="error-message"></div>
<div id="success-message" class="success-message"></div>
<div id="stats-container" class="stats-container">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="total-count">0</div>
<div class="stat-label">Total URLs</div>
</div>
<div class="stat-card">
<div class="stat-value" id="unique-count">0</div>
<div class="stat-label">Unique URLs</div>
</div>
<div class="stat-card">
<div class="stat-value" id="internal-count">0</div>
<div class="stat-label">Internal Links</div>
</div>
<div class="stat-card">
<div class="stat-value" id="external-count">0</div>
<div class="stat-label">External Links</div>
</div>
</div>
<div class="url-types" id="url-types"></div>
</div>
</div>
<div class="right-panel">
<div class="results-header">
<div class="panel-title">
<span class="panel-icon">📋</span>
<span>Extracted URLs</span>
</div>
</div>
<div class="filter-controls">
<div class="filter-group">
<span class="filter-label">Filter:</span>
<input
type="text"
id="filter-input"
class="filter-input"
placeholder="Filter by domain or keyword"
>
</div>
<div class="filter-group">
<span class="filter-label">Type:</span>
<select id="type-filter" class="filter-select">
<option value="all">All Types</option>
<option value="internal">Internal Links</option>
<option value="external">External Links</option>
<option value="image">Images</option>
<option value="css">CSS Files</option>
<option value="js">JavaScript Files</option>
</select>
</div>
<div class="checkbox-group">
<input type="checkbox" id="unique-only" checked>
<label for="unique-only" class="checkbox-label">Unique only</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="show-badges" checked>
<label for="show-badges" class="checkbox-label">Show badges</label>
</div>
</div>
<div class="results-content">
<div id="results"></div>
</div>
<div class="action-buttons">
<button id="sort-button" class="action-button secondary">Sort A-Z</button>
<button id="remove-dupes-button" class="action-button warning">Remove Duplicates</button>
<button id="copy-button" class="action-button primary">Copy All</button>
<button id="clear-button" class="action-button danger">Clear All</button>
</div>
</div>
</div>
<div class="status-bar">
<span id="status">Ready. Enter a URL to begin.</span>
<span id="url-count">0 URLs</span>
</div>
</div>
</div>
<script>
const urlInput = document.getElementById('url-input');
const fetchButton = document.getElementById('fetch-button');
const results = document.getElementById('results');
const copyButton = document.getElementById('copy-button');
const clearButton = document.getElementById('clear-button');
const removeDupesButton = document.getElementById('remove-dupes-button');
const sortButton = document.getElementById('sort-button');
const errorMessage = document.getElementById('error-message');
const successMessage = document.getElementById('success-message');
const statsContainer = document.getElementById('stats-container');
const filterInput = document.getElementById('filter-input');
const typeFilter = document.getElementById('type-filter');
const uniqueOnlyCheckbox = document.getElementById('unique-only');
const showBadgesCheckbox = document.getElementById('show-badges');
const status = document.getElementById('status');
const urlCount = document.getElementById('url-count');
const proxyInfo = document.getElementById('proxy-info');
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = document.getElementById('theme-icon');
const themeText = document.getElementById('theme-text');
const loadingOverlay = document.getElementById('loading-overlay');
const totalCountElement = document.getElementById('total-count');
const uniqueCountElement = document.getElementById('unique-count');
const internalCountElement = document.getElementById('internal-count');
const externalCountElement = document.getElementById('external-count');
const urlTypesElement = document.getElementById('url-types');
let extractedUrls = [];
let currentFilter = '';
let currentTypeFilter = 'all';
let showUniqueOnly = true;
let showBadges = true;
const CORS_PROXIES = [
'https://api.allorigins.win/raw?url=',
'https://corsproxy.io/?',
'https://api.codetabs.com/v1/proxy?quest='
];
const isInIframe = window.self !== window.top;
if (isInIframe) {
console.log('Running in iframe - applying optimizations');
document.body.classList.add('in-iframe');
const sendHeightToParent = () => {
const height = document.documentElement.scrollHeight;
window.parent.postMessage({
type: 'iframeHeight',
height: height
}, '*');
};
setTimeout(sendHeightToParent, 100);
const observer = new MutationObserver(sendHeightToParent);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
window.addEventListener('resize', sendHeightToParent);
}
function init() {
const savedUrl = localStorage.getItem('lastFetchedUrl');
if (savedUrl) {
urlInput.value = savedUrl;
}
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light-mode');
themeIcon.textContent = '🌙';
themeText.textContent = 'Dark Mode';
} else {
themeIcon.textContent = '☀️';
themeText.textContent = 'Light Mode';
}
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
fetchAndExtract();
}
});
fetchButton.addEventListener('click', fetchAndExtract);
copyButton.addEventListener('click', copyResults);
clearButton.addEventListener('click', clearAll);
removeDupesButton.addEventListener('click', removeDuplicates);
sortButton.addEventListener('click', sortResults);
filterInput.addEventListener('input', updateFilter);
typeFilter.addEventListener('change', updateTypeFilter);
uniqueOnlyCheckbox.addEventListener('change', updateUniqueFilter);
showBadgesCheckbox.addEventListener('change', updateBadgesDisplay);
proxyInfo.addEventListener('click', showProxyInfo);
themeToggle.addEventListener('click', toggleTheme);
urlInput.focus();
}
async function fetchAndExtract() {
const url = urlInput.value.trim();
if (!url) {
showError('Please enter a valid URL');
urlInput.focus();
return;
}
try {
new URL(url.startsWith('http') ? url : \`https://\${url}\`);
} catch {
showError('Please enter a valid URL (e.g., https://example.com)');
urlInput.focus();
return;
}
// Add https:// if not present
const fullUrl = url.startsWith('http') ? url : \`https://\${url}\`;
localStorage.setItem('lastFetchedUrl', fullUrl);
fetchButton.disabled = true;
fetchButton.classList.add('loading');
fetchButton.textContent = 'Fetching...';
loadingOverlay.classList.add('visible');
clearResults();
hideMessages();
showStatus(\`Fetching \${new URL(fullUrl).hostname}...\`);
try {
let html = null;
let lastError = null;
for (let i = 0; i < CORS_PROXIES.length; i++) {
const proxyUrl = CORS_PROXIES[i] + encodeURIComponent(fullUrl);
try {
showStatus(\`Trying proxy \${i + 1}/\${CORS_PROXIES.length}...\`);
const response = await fetchWithTimeout(proxyUrl, 15000);
if (!response.ok) {
throw new Error(\`HTTP \${response.status}\`);
}
html = await response.text();
break;
} catch (error) {
lastError = error;
console.warn(\`Proxy \${i} failed:\`, error);
}
}
if (!html) {
throw new Error(\`All proxies failed. Last error: \${lastError?.message || 'Unknown error'}\`);
}
showStatus('Extracting URLs from HTML...');
const urls = extractUrlsFromHTML(html, fullUrl);
if (urls.length === 0) {
throw new Error('No URLs found on this webpage');
}
extractedUrls = urls;
displayResults();
updateStats();
showSuccess(\`Successfully extracted \${urls.length} URLs from \${new URL(fullUrl).hostname}\`);
statsContainer.style.display = 'block';
} catch (error) {
console.error('Fetch error:', error);
showError(\`Failed to fetch webpage: \${error.message}. Try a different URL or check your connection.\`);
} finally {
fetchButton.disabled = false;
fetchButton.classList.remove('loading');
fetchButton.textContent = 'Fetch & Extract URLs';
loadingOverlay.classList.remove('visible');
}
}
function extractUrlsFromHTML(html, baseUrl) {
const urls = [];
const base = new URL(baseUrl);
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const urlAttributes = [
{ selector: 'a[href]', attr: 'href' },
{ selector: 'img[src]', attr: 'src' },
{ selector: 'img[srcset]', attr: 'srcset' },
{ selector: 'link[href]', attr: 'href' },
{ selector: 'script[src]', attr: 'src' },
{ selector: 'iframe[src]', attr: 'src' },
{ selector: 'source[src]', attr: 'src' },
{ selector: 'video[src]', attr: 'src' },
{ selector: 'audio[src]', attr: 'src' },
{ selector: 'embed[src]', attr: 'src' },
{ selector: 'object[data]', attr: 'data' },
{ selector: 'form[action]', attr: 'action' },
{ selector: 'meta[content]', extractor: (el) => {
const property = el.getAttribute('property');
const itemprop = el.getAttribute('itemprop');
if (property && ['og:image', 'og:url', 'og:video'].includes(property)) {
return el.getAttribute('content');
}
if (itemprop && ['image', 'url'].includes(itemprop)) {
return el.getAttribute('content');
}
return null;
}},
{ selector: '*[style*="url("]', extractor: extractFromStyle },
{ selector: '*[data-src]', attr: 'data-src' },
{ selector: '*[data-href]', attr: 'data-href' },
{ selector: '*[data-url]', attr: 'data-url' }
];
urlAttributes.forEach(({ selector, attr, extractor }) => {
const elements = doc.querySelectorAll(selector);
elements.forEach(element => {
let url = null;
if (extractor) {
url = extractor(element);
} else if (attr === 'srcset') {
const srcset = element.getAttribute('srcset');
if (srcset) {
srcset.split(',').forEach(part => {
const src = part.trim().split(' ')[0];
if (src) url = src;
});
}
} else {
url = element.getAttribute(attr);
}
if (url && url.trim() && !url.startsWith('javascript:') && !url.startsWith('mailto:')) {
const cleanUrl = cleanAndNormalizeUrl(url.trim(), base);
if (cleanUrl) {
urls.push({
url: cleanUrl,
type: getUrlType(cleanUrl, base),
original: url.trim()
});
}
}
});
});
const scripts = doc.querySelectorAll('script:not([src]), style');
scripts.forEach(element => {
const text = element.textContent;
const urlRegex = /(https?:\\/\\/[^\\s<>"\\'{}|\\\\^\`\\[\\]]+)|(src\\s*=\\s*["']([^"']+)["'])|(href\\s*=\\s*["']([^"']+)["'])|(url\\(["']?([^"')]+)["']?\\))/gi;
let match;
while ((match = urlRegex.exec(text)) !== null) {
const url = match[1] || match[3] || match[5] || match[7];
if (url && url.trim()) {
const cleanUrl = cleanAndNormalizeUrl(url.trim(), base);
if (cleanUrl) {
urls.push({
url: cleanUrl,
type: getUrlType(cleanUrl, base),
original: url.trim()
});
}
}
}
});
return urls;
}
function extractFromStyle(element) {
const style = element.getAttribute('style');
if (!style) return null;
const urlMatch = style.match(/url\\(['"]?([^'")]+)['"]?\\)/i);
return urlMatch ? urlMatch[1] : null;
}
function cleanAndNormalizeUrl(url, base) {
if (url.startsWith('data:') || url.startsWith('#') || url === '') {
return null;
}
url = url.split('#')[0];
try {
if (url.startsWith('//')) {
url = base.protocol + url;
} else if (url.startsWith('/')) {
url = base.origin + url;
} else if (!url.startsWith('http')) {
url = new URL(url, base.origin).href;
}
const urlObj = new URL(url);
const trackingParams = ['utm_', 'fbclid', 'gclid', 'msclkid', 'dclid', 'ref', 'source'];
urlObj.searchParams.forEach((value, key) => {
if (trackingParams.some(param => key.startsWith(param))) {
urlObj.searchParams.delete(key);
}
});
let cleanUrl = urlObj.href;
if (cleanUrl.endsWith('/')) {
cleanUrl = cleanUrl.slice(0, -1);
}
return cleanUrl;
} catch {
return url;
}
}
function getUrlType(url, base) {
try {
const urlObj = new URL(url);
const baseObj = new URL(base.origin);
const isInternal = urlObj.hostname === baseObj.hostname ||
urlObj.hostname.endsWith('.' + baseObj.hostname) ||
!urlObj.hostname.includes('.');
const pathname = urlObj.pathname.toLowerCase();
if (pathname.match(/\\.(jpg|jpeg|png|gif|webp|svg|ico|bmp|tiff)$/)) {
return isInternal ? 'internal-image' : 'external-image';
}
if (pathname.match(/\\.css$/)) {
return isInternal ? 'internal-css' : 'external-css';
}
if (pathname.match(/\\.js$/)) {
return isInternal ? 'internal-js' : 'external-js';
}
if (pathname.match(/\\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/)) {
return isInternal ? 'internal-doc' : 'external-doc';
}
return isInternal ? 'internal' : 'external';
} catch {
return 'external';
}
}
function displayResults() {
let filteredUrls = [...extractedUrls];
if (currentFilter) {
const filterLower = currentFilter.toLowerCase();
filteredUrls = filteredUrls.filter(item =>
item.url.toLowerCase().includes(filterLower) ||
item.original.toLowerCase().includes(filterLower)
);
}
if (currentTypeFilter !== 'all') {
filteredUrls = filteredUrls.filter(item =>
item.type.includes(currentTypeFilter)
);
}
if (showUniqueOnly) {
const seen = new Set();
filteredUrls = filteredUrls.filter(item => {
if (seen.has(item.url)) return false;
seen.add(item.url);
return true;
});
}
if (filteredUrls.length === 0) {
results.innerHTML = '<div style="text-align: center; padding: 40px; color: var(--text-muted);">No URLs match the current filters.</div>';
urlCount.textContent = '0 URLs';
return;
}
let output = '';
filteredUrls.forEach(item => {
if (showBadges) {
const badgeClass = getBadgeClass(item.type);
const badgeText = getBadgeText(item.type);
output += \`<div class="url-item">
<span class="url-badge \${badgeClass}">\${badgeText}</span>
<span class="url-text">\${escapeHtml(item.url)}</span>
</div>\`;
} else {
output += \`<div class="url-item">
<span class="url-text">\${escapeHtml(item.url)}</span>
</div>\`;
}
});
results.innerHTML = output;
urlCount.textContent = \`\${filteredUrls.length} URL\${filteredUrls.length !== 1 ? 's' : ''}\`;
if (isInIframe) {
setTimeout(() => {
const height = document.documentElement.scrollHeight;
window.parent.postMessage({
type: 'iframeHeight',
height: height
}, '*');
}, 100);
}
}
function getBadgeClass(type) {
if (type.includes('image')) return 'badge-image';
if (type.includes('css')) return 'badge-css';
if (type.includes('js')) return 'badge-js';
if (type.includes('doc')) return 'badge-external';
if (type.includes('internal')) return 'badge-internal';
return 'badge-external';
}
function getBadgeText(type) {
if (type.includes('image')) return 'IMG';
if (type.includes('css')) return 'CSS';
if (type.includes('js')) return 'JS';
if (type.includes('internal')) return 'INT';
return 'EXT';
}
function updateStats() {
const total = extractedUrls.length;
const unique = new Set(extractedUrls.map(u => u.url)).size;
const internal = extractedUrls.filter(u => u.type.includes('internal')).length;
const external = extractedUrls.filter(u => u.type.includes('external')).length;
totalCountElement.textContent = total;
uniqueCountElement.textContent = unique;
internalCountElement.textContent = internal;
externalCountElement.textContent = external;
const typeCounts = {};
extractedUrls.forEach(item => {
const mainType = item.type.includes('internal') ? 'internal' :
item.type.includes('external') ? 'external' : 'other';
typeCounts[mainType] = (typeCounts[mainType] || 0) + 1;
});
urlTypesElement.innerHTML = '';
Object.entries(typeCounts).forEach(([type, count]) => {
const tag = document.createElement('span');
tag.className = 'url-type-tag';
tag.textContent = \`\${type}: \${count}\`;
urlTypesElement.appendChild(tag);
});
}
function updateFilter() {
currentFilter = filterInput.value;
displayResults();
}
function updateTypeFilter() {
currentTypeFilter = typeFilter.value;
displayResults();
}
function updateUniqueFilter() {
showUniqueOnly = uniqueOnlyCheckbox.checked;
displayResults();
}
function updateBadgesDisplay() {
showBadges = showBadgesCheckbox.checked;
displayResults();
}
function fetchWithTimeout(url, timeout) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.add('visible');
showStatus('Error: ' + message);
}
function showSuccess(message) {
successMessage.textContent = message;
successMessage.classList.add('visible');
showStatus(message);
}
function hideMessages() {
errorMessage.classList.remove('visible');
successMessage.classList.remove('visible');
}
function showStatus(message) {
status.textContent = message;
}
function clearResults() {
results.innerHTML = '';
statsContainer.style.display = 'none';
extractedUrls = [];
urlCount.textContent = '0 URLs';
}
function clearAll() {
urlInput.value = '';
clearResults();
hideMessages();
showStatus('Ready. Enter a URL to begin.');
localStorage.removeItem('lastFetchedUrl');
urlInput.focus();
}
function copyResults() {
if (extractedUrls.length === 0) {
showError('No URLs to copy. Please fetch a webpage first.');
return;
}
const text = extractedUrls.map(u => u.url).join('\\n');
navigator.clipboard.writeText(text).then(() => {
const originalText = copyButton.textContent;
copyButton.textContent = '✓ Copied!';
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
showStatus(\`Copied \${extractedUrls.length} URLs to clipboard\`);
}).catch(err => {
console.error('Failed to copy:', err);
showError('Failed to copy to clipboard. Please try again.');
});
}
function removeDuplicates() {
const seen = new Set();
extractedUrls = extractedUrls.filter(item => {
if (seen.has(item.url)) return false;
seen.add(item.url);
return true;
});
displayResults();
updateStats();
showStatus(\`Removed duplicates. \${extractedUrls.length} unique URLs remaining.\`);
}
function sortResults() {
extractedUrls.sort((a, b) => a.url.localeCompare(b.url));
displayResults();
showStatus('Sorted URLs alphabetically');
}
function showProxyInfo(e) {
e.preventDefault();
alert(
"This tool uses CORS proxies to bypass browser security restrictions.\\n\\n" +
"CORS proxies allow fetching content from other websites by acting as intermediaries.\\n\\n" +
"Note: Some websites may block proxy requests. If one proxy fails, the tool automatically tries others."
);
}
function toggleTheme() {
const isLightMode = document.documentElement.classList.contains('light-mode');
if (isLightMode) {
document.documentElement.classList.remove('light-mode');
themeIcon.textContent = '☀️';
themeText.textContent = 'Light Mode';
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.add('light-mode');
themeIcon.textContent = '🌙';
themeText.textContent = 'Dark Mode';
localStorage.setItem('theme', 'light');
}
}
init();
</script>
</body>
</html>