// ==UserScript==
// @name Universal Link Extractor
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Estrae link con filtri di inclusione specifici per ogni sito, con GUI e toggle.
// @author ChatGPT (modificato)
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const currentHost = location.host;
const enabledSites = JSON.parse(GM_getValue('enabledSites', '[]'));
// Struttura per salvare i filtri specifici per ogni sito
// { "example.com": ["pattern1", "pattern2"], "another.com": ["pattern3"] }
const siteFilters = JSON.parse(GM_getValue('siteFilters', '{}'));
// Menu per abilitare/disabilitare lo script su questo sito
GM_registerMenuCommand(
enabledSites.includes(currentHost)
? 'Disabilita Universal Link Extractor su questo sito'
: 'Abilita Universal Link Extractor su questo sito',
() => {
let sites = JSON.parse(GM_getValue('enabledSites', '[]'));
if (sites.includes(currentHost)) {
sites = sites.filter(s => s !== currentHost);
GM_setValue('enabledSites', JSON.stringify(sites));
alert('Script disabilitato su: ' + currentHost);
} else {
sites.push(currentHost);
GM_setValue('enabledSites', JSON.stringify(sites));
// Se non esistono filtri per questo sito, crea un default
if (!siteFilters[currentHost]) {
siteFilters[currentHost] = [`^https://${currentHost}/.*`];
GM_setValue('siteFilters', JSON.stringify(siteFilters));
}
alert('Script abilitato su: ' + currentHost);
}
}
);
// Se non abilitato, esci
if (!enabledSites.includes(currentHost)) return;
// GUI principale
const panel = document.createElement('div');
Object.assign(panel.style, {
position: 'fixed', top: '50px', right: '10px',
width: '360px', maxHeight: '300px', overflowY: 'auto', overflowX: 'hidden',
backgroundColor: 'rgba(0,0,0,0.85)', color: '#fff',
border: '1px solid #444', borderRadius: '4px',
boxShadow: '0 2px 8px rgba(0,0,0,0.6)', zIndex: '999999',
fontFamily: 'Arial, sans-serif', fontSize: '14px', lineHeight: '1.4',
transition: 'max-height 0.3s ease, opacity 0.3s ease', opacity: '1'
});
// Ottieni i filtri del sito corrente
const currentSiteFilters = siteFilters[currentHost] || [`^https://${currentHost}/.*`];
panel.innerHTML = `
<div style="padding:12px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<strong>Universal Link Extractor</strong>
<button id="extractBtn" style="background:#28a745; border:none; color:#fff; padding:4px 8px; border-radius:3px; cursor:pointer;">Estrai link</button>
</div>
<div style="font-weight:bold; margin-bottom:8px;">Come usare:</div>
<ul style="padding-left:20px; margin:0 0 8px 0;">
<li>Clicca "Estrai link" per copiare quelli che matchano i filtri.</li>
<li>Configura filtri di <strong>inclusione</strong> specifici per ${currentHost}.</li>
</ul>
<div style="font-weight:bold; margin-bottom:4px;">Filtri attivi per ${currentHost}:</div>
<pre style="background:#f4f4f4; color:#000; padding:10px; border-radius:4px; font-family:monospace; font-size:13px; max-height:80px; overflow-y:auto;">
${currentSiteFilters.join('\n')}
</pre>
<div style="font-size:12px; color:#ccc; margin-top:8px;">Apri/chiudi con la freccia blu in alto a destra.</div>
</div>
`;
document.body.appendChild(panel);
// Listener Estrai
document.getElementById('extractBtn').addEventListener('click', extractLinks);
// Toggle sempre visibile
const toggle = document.createElement('div');
Object.assign(toggle.style, {
position: 'fixed', top: '10px', right: '10px',
width: '32px', height: '32px', cursor: 'pointer',
backgroundColor: '#007bff', color: '#fff', display: 'flex',
alignItems: 'center', justifyContent: 'center',
borderRadius: '4px', fontSize: '18px', fontWeight: 'bold',
boxShadow: '0 2px 4px rgba(0,0,0,0.5)', zIndex: '1000000',
userSelect: 'none', transition: 'opacity 0.3s ease', opacity: '1'
});
toggle.textContent = '▼';
document.body.appendChild(toggle);
let expanded = true;
toggle.addEventListener('click', () => {
expanded = !expanded;
if (expanded) {
panel.style.maxHeight = '300px'; panel.style.opacity = '1'; toggle.textContent = '▼'; toggle.style.opacity = '1';
} else {
panel.style.maxHeight = '0'; panel.style.opacity = '0'; toggle.textContent = '▲'; toggle.style.opacity = '0';
}
});
// Menu comando per configurare i filtri specifici per il sito
GM_registerMenuCommand(`Configura filtri di inclusione per ${currentHost}`, () => {
const defaultVal = currentSiteFilters.join(',');
const inp = prompt(`Regex da includere per ${currentHost} (separa con virgola):`, defaultVal);
if (inp !== null) {
const newFilters = inp.split(',').map(s => s.trim()).filter(s => s);
// Aggiorna i filtri specifici per questo sito
siteFilters[currentHost] = newFilters;
GM_setValue('siteFilters', JSON.stringify(siteFilters));
alert(`Filtri di inclusione aggiornati per ${currentHost}`);
// Refresh per aggiornare l'interfaccia
location.reload();
}
});
// Estrai link basandosi solo sui filtri del sito corrente
function matchesAny(link, patterns) {
return patterns.some(pat => {
try {
return new RegExp(pat).test(link);
} catch {
return false;
}
});
}
function extractLinks() {
const currentFilters = siteFilters[currentHost] || [`^https://${currentHost}/.*`];
const links = Array.from(document.querySelectorAll('a[href]'))
.map(a => a.href)
.filter(l => matchesAny(l, currentFilters));
if (!links.length) return alert('Nessun link trovato');
const count = links.length;
navigator.clipboard.writeText(links.join('\n')).then(() => {
console.log('Link estratti:', links);
alert(`Ho copiato ${count} link negli appunti`);
});
}
})();