Universal Link Extractor

Estrae link con filtri di inclusione specifici per ogni sito, con GUI e toggle.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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`);
        });
    }
})();