Decrypt FileCrypt Links

Decrypt all FileCrypt links

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Decrypt FileCrypt Links
// @version      3.0
// @description  Decrypt all FileCrypt links
// @author       SH3LL
// @grant        GM.xmlHttpRequest
// @match        *://filecrypt.cc/*
// @match        *://www.filecrypt.cc/*
// @match        *://filecrypt.co/*
// @match        *://www.filecrypt.co/*
// @run-at       document-end
// @connect      self
// @connect      *
// @namespace https://greasyfork.org/users/762057
// ==/UserScript==

(function() {
    'use strict';

    // ==================== CONFIGURATION ====================

    const CONFIG = {
        hostname: document.location.hostname,
        isDarkMode: !!document.head.querySelector('meta[name="theme-color"]')
    };

    const STYLES = {
        get bgColor() { return CONFIG.isDarkMode ? '#0b0d15' : 'white'; },
        get textColor() { return CONFIG.isDarkMode ? 'white' : 'black'; },
        get borderColor() { return CONFIG.isDarkMode ? '#444' : '#ddd'; },
        get buttonBg() { return CONFIG.isDarkMode ? '#333' : '#f0f0f0'; },
        get buttonHoverBg() { return CONFIG.isDarkMode ? '#555' : '#e0e0e0'; },
        get groupBg() { return CONFIG.isDarkMode ? '#1a1d2e' : '#f9f9f9'; }
    };

    // Storage for grouped links and UI elements
    const linkGroups = new Map();
    const groupElements = new Map();
    let mainContainer = null;
    let totalLinksCount = 0;
    let pendingCount = 0;

    // ==================== INITIALIZATION ====================

    function init() {
        removeUsenetAds();
        injectStyles();

        const path = document.location.href;

        if (path.includes('/Link/')) {
            handleSingleLink();
        } else if (path.includes('/Container/')) {
            handleContainer();
        } else {
            const links = document.querySelectorAll('a[href*="Link/"]');
            if (links.length > 0) {
                handleContainer();
            }
        }
    }

    function injectStyles() {
        const css = `
            .fc-bypass-container {
                background-color: ${STYLES.bgColor};
                border-radius: 10px;
                padding: 1em;
                margin: 1em 0;
                color: ${STYLES.textColor};
                z-index: 10;
                position: relative;
            }
            .fc-bypass-header {
                font-size: 1.2em;
                font-weight: bold;
                margin-bottom: 0.5em;
            }
            .fc-bypass-status {
                font-size: 0.9em;
                color: ${CONFIG.isDarkMode ? '#aaa' : '#666'};
                margin-bottom: 1em;
            }
            .fc-bypass-groups-wrapper {
                display: flex;
                flex-wrap: wrap;
                gap: 1em;
                align-items: flex-start;
                justify-content: center;
            }
            .fc-bypass-group {
                background-color: ${STYLES.groupBg};
                border: 1px solid ${STYLES.borderColor};
                border-radius: 8px;
                padding: 1em;
                display: inline-block;
                min-width: 200px;
                max-width: 100%;
            }
            .fc-bypass-group-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                gap: 1em;
                margin-bottom: 0.8em;
                padding-bottom: 0.5em;
                border-bottom: 1px solid ${STYLES.borderColor};
            }
            .fc-bypass-group-title {
                font-weight: bold;
                color: ${STYLES.textColor};
            }
            .fc-bypass-links-container {
                display: flex;
                flex-direction: column;
                gap: 0.4em;
            }
            .fc-bypass-link-row {
                display: inline-flex;
                align-items: center;
                gap: 0.5em;
            }
            .fc-bypass-link {
                color: ${STYLES.textColor};
                cursor: pointer;
                word-break: break-all;
            }
            .fc-bypass-link:hover {
                text-decoration: underline;
            }
            .fc-bypass-btn {
                background-color: ${STYLES.buttonBg};
                color: ${STYLES.textColor};
                border: 1px solid ${STYLES.borderColor};
                border-radius: 4px;
                padding: 0.3em 0.6em;
                cursor: pointer;
                font-size: 0.85em;
                white-space: nowrap;
                flex-shrink: 0;
            }
            .fc-bypass-btn:hover {
                background-color: ${STYLES.buttonHoverBg};
            }
            .fc-bypass-btn:disabled {
                opacity: 0.6;
                cursor: default;
            }
            .fc-bypass-btn-primary {
                background-color: #4a90d9;
                color: white;
                border-color: #3a7bc8;
            }
            .fc-bypass-btn-primary:hover {
                background-color: #3a7bc8;
            }
            .fc-bypass-controls {
                display: flex;
                gap: 0.5em;
                margin-bottom: 1em;
                flex-wrap: wrap;
                justify-content: center;
            }
        `;

        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    // ==================== AD REMOVAL ====================

    function removeUsenetAds() {
        const allLinks = document.getElementsByTagName('A');
        for (const link of allLinks) {
            if (link.href && link.href.includes('/pink/')) {
                link.parentNode?.remove();
                break;
            }
        }

        const adSelectors = [
            '[class*="usenet"]',
            '[class*="sponsor"]',
            '[id*="usenet"]',
            '[id*="sponsor"]'
        ];

        for (const selector of adSelectors) {
            const ads = document.querySelectorAll(selector);
            for (const ad of ads) {
                ad.remove();
            }
        }
    }

    // ==================== SINGLE LINK HANDLING ====================

    function handleSingleLink() {
        console.log('[Bypass] Handling single link page');

        const linkFromDom = extractLinkFromDOM();
        if (linkFromDom) {
            console.log('[Bypass] Link trovato nel DOM:', linkFromDom);
            resolveFinalLink(linkFromDom);
            return;
        }

        const linkFromScripts = extractLinkFromScripts();
        if (linkFromScripts) {
            console.log('[Bypass] Link trovato negli script:', linkFromScripts);
            resolveFinalLink(linkFromScripts);
            return;
        }

        if (document.body.children.length > 0) {
            console.log('[Bypass] Cerco link nel body HTML');
            const linkFromBody = extractIntermediateLink(document.body.innerHTML);
            if (linkFromBody) {
                resolveFinalLink(linkFromBody);
                return;
            }
        }

        console.log('[Bypass] Faccio fetch della pagina');
        fetchAndExtractLink();
    }

    function extractLinkFromDOM() {
        const links = document.querySelectorAll('a[href*="Link/"], a[href*="filecrypt"]');
        for (const link of links) {
            if (link.href && link.href.includes('/Link/')) {
                return link.href;
            }
        }
        return null;
    }

    function extractLinkFromScripts() {
        const scripts = document.getElementsByTagName('SCRIPT');
        for (const script of scripts) {
            const content = script.innerHTML;

            let match = content.match(/top\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/window\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/window\.open\(['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/self\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/parent\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];
        }
        return null;
    }

    function fetchAndExtractLink() {
        GM.xmlHttpRequest({
            method: 'GET',
            url: document.location.href,
            onload: (response) => {
                console.log('[Bypass] Fetch completato, estraggo link');
                const intermediateLink = extractIntermediateLink(response.responseText);
                if (intermediateLink) {
                    console.log('[Bypass] Link estratto dal fetch:', intermediateLink);
                    resolveFinalLink(intermediateLink);
                } else {
                    console.log('[Bypass] Nessun link trovato nel fetch');
                }
            },
            onerror: (error) => {
                console.log('[Bypass] Errore nel fetch:', error);
            }
        });
    }

    function resolveFinalLink(intermediateLink) {
        if (!intermediateLink) {
            console.log('[Bypass] Link intermedio vuoto');
            return;
        }

        if (intermediateLink.startsWith('http') &&
            !intermediateLink.includes('filecrypt') &&
            !intermediateLink.includes('/Link/')) {
            console.log('[Bypass] URL diretto, navigo:', intermediateLink);
            window.location.href = intermediateLink;
            return;
        }

        console.log('[Bypass] Risolvo link intermedio:', intermediateLink);

        GM.xmlHttpRequest({
            method: 'GET',
            url: intermediateLink,
            onload: (response) => {
                console.log('[Bypass] Risposta ricevuta per:', intermediateLink);

                if (response.finalUrl && response.finalUrl !== intermediateLink) {
                    console.log('[Bypass] Redirect a:', response.finalUrl);
                    window.location.href = response.finalUrl;
                    return;
                }

                const extracted = extractIntermediateLink(response.responseText);
                if (extracted && extracted !== intermediateLink) {
                    console.log('[Bypass] Link estratto dalla risposta:', extracted);
                    window.location.href = extracted;
                } else {
                    console.log('[Bypass] Uso link intermedio come ultima spiaggia');
                    window.location.href = intermediateLink;
                }
            },
            onerror: (error) => {
                console.log('[Bypass] Errore nella risoluzione:', error);
                window.location.href = intermediateLink;
            }
        });
    }

    function extractIntermediateLink(html) {
        if (!html) return null;

        const patterns = [
            /top\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /window\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /location\.href\s*=\s*['"]([^'"]+)['"]/,
            /self\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /parent\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /window\.open\(['"]([^'"]+)['"]/,
            /href="([^"]*\/Link\/[^"]*\.html)"/,
            /"([^"]*\/Link\/[^"]*\.html)"/,
            /id=([a-zA-Z0-9]+)/
        ];

        for (const pattern of patterns) {
            const match = html.match(pattern);
            if (match && match[1]) {
                let link = match[1];
                if (!link.startsWith('http')) {
                    link = `https://${CONFIG.hostname}/Link/${link}.html`;
                }
                return link.replace(/&/g, '&');
            }
        }

        const lastHttpIndex = html.lastIndexOf('http');
        if (lastHttpIndex !== -1) {
            const idIndex = html.indexOf('id=', lastHttpIndex);
            if (idIndex !== -1) {
                const url = html.substring(lastHttpIndex, idIndex + 43);
                return url.replace(/&/g, '&');
            }
        }

        return null;
    }

    // ==================== CONTAINER HANDLING ====================

    function handleContainer() {
        console.log('[Bypass] Handling container page');

        const downloadBtn = document.querySelector('.download');
        if (!downloadBtn) {
            console.log('[Bypass] Nessun pulsante download trovato');
            return;
        }

        const article = downloadBtn.closest('article')
                     || downloadBtn.parentNode?.parentNode?.parentNode?.parentNode;

        if (!article?.parentNode) {
            console.log('[Bypass] Articolo non trovato');
            return;
        }

        mainContainer = createMainContainer();
        article.parentNode.insertBefore(mainContainer, article);

        extractLinksLocally();
    }

    function createMainContainer() {
        const container = document.createElement('DIV');
        container.className = 'fc-bypass-container';
        container.id = 'fc-bypass-main';

        const header = document.createElement('DIV');
        header.className = 'fc-bypass-header';
        header.textContent = 'Decrypted Links';
        container.appendChild(header);

        const status = document.createElement('DIV');
        status.className = 'fc-bypass-status';
        status.id = 'fc-bypass-status';
        status.textContent = 'Decrypting links...';
        container.appendChild(status);

        const controls = document.createElement('DIV');
        controls.className = 'fc-bypass-controls';
        controls.id = 'fc-bypass-controls';

        const copyAllBtn = createButton('Copy All Links', 'fc-bypass-btn fc-bypass-btn-primary', copyAllLinks);
        copyAllBtn.id = 'fc-bypass-copy-all';
        controls.appendChild(copyAllBtn);
        container.appendChild(controls);

        const groupsWrapper = document.createElement('DIV');
        groupsWrapper.className = 'fc-bypass-groups-wrapper';
        groupsWrapper.id = 'fc-bypass-groups';
        container.appendChild(groupsWrapper);

        return container;
    }

    // ==================== LINK EXTRACTION ====================

    function extractLinksLocally() {
        const encryptedButtons = findEncryptedButtons();

        if (encryptedButtons.length === 0) {
            updateStatus('No download links found on this page.', true);
            return;
        }

        pendingCount = encryptedButtons.length;
        console.log(`[Bypass] Trovati ${pendingCount} link crittati`);
        updateStatus(`Decrypting ${pendingCount} links...`);

        encryptedButtons.forEach(button => processEncryptedButton(button));
    }

    function findEncryptedButtons() {
        let buttons = document.querySelectorAll("button.download[onclick*='openLink']");

        if (buttons.length === 0) {
            buttons = document.querySelectorAll("[onclick*='openLink']");
        }

        if (buttons.length === 0) {
            buttons = document.querySelectorAll("button[data-id], a[data-id]");
        }

        return Array.from(buttons);
    }

    function processEncryptedButton(button) {
        const onclick = button.getAttribute('onclick');
        let encryptedId = null;

        if (onclick) {
            let match = onclick.match(/this\.getAttribute\(['"]([^'"]+)['"]\)/);
            if (match && match[1]) {
                encryptedId = button.getAttribute(match[1]);
            }

            if (!encryptedId) {
                match = onclick.match(/getAttribute\(['"]([^'"]+)['"]\)/);
                if (match && match[1]) {
                    encryptedId = button.getAttribute(match[1]);
                }
            }

            if (!encryptedId) {
                match = onclick.match(/\/Link\/([^'"\.]+)\.html/);
                if (match && match[1]) {
                    encryptedId = match[1];
                }
            }
        }

        if (!encryptedId) {
            for (const attr of button.attributes) {
                if (attr.name.startsWith('data-') && attr.value) {
                    encryptedId = attr.value;
                    break;
                }
            }
        }

        if (!encryptedId && button.dataset) {
            for (const key in button.dataset) {
                if (button.dataset[key]) {
                    encryptedId = button.dataset[key];
                    break;
                }
            }
        }

        if (!encryptedId) {
            console.log('[Bypass] ID non trovato per il pulsante');
            onLinkProcessed();
            return;
        }

        console.log('[Bypass] ID trovato:', encryptedId);

        const row = button.closest('tr');
        let linkStatus = 'unknown';
        if (row) {
            const statusIcon = row.querySelector('td.status i');
            if (statusIcon) {
                if (statusIcon.classList.contains('online')) {
                    linkStatus = 'online';
                } else if (statusIcon.classList.contains('offline')) {
                    linkStatus = 'offline';
                }
            }
        }

        const linkUrl = `https://${CONFIG.hostname}/Link/${encryptedId}.html`;
        fetchAndDecryptLink(linkUrl, linkStatus);
    }

    function fetchAndDecryptLink(url, linkStatus = 'unknown') {
        console.log('[Bypass] Decripto:', url);

        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            onload: (response) => {
                console.log('[Bypass] Risposta ricevuta per:', url);
                const parser = new DOMParser();
                const doc = parser.parseFromString(response.responseText, 'text/html');
                const redirectUrl = findRedirectUrl(doc);

                if (redirectUrl) {
                    console.log('[Bypass] URL di redirect trovato:', redirectUrl);
                    resolveAndDisplayLink(redirectUrl, linkStatus);
                } else {
                    console.log('[Bypass] Nessun redirect trovato');
                    onLinkProcessed();
                }
            },
            onerror: (error) => {
                console.log(`[Bypass] Errore nel fetch: ${url}`, error);
                onLinkProcessed();
            }
        });
    }

    function findRedirectUrl(doc) {
        const scripts = doc.getElementsByTagName('SCRIPT');

        for (const script of scripts) {
            const content = script.innerHTML;

            let match = content.match(/top\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/window\.location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/location\.href\s*=\s*['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];

            match = content.match(/window\.open\(['"]([^'"]+)['"]/);
            if (match && match[1]) return match[1];
        }

        return null;
    }

    // ==================== Resolve Links ====================

    function resolveAndDisplayLink(encryptedUrl, linkStatus = 'unknown') {
        console.log('[Bypass] Risolvo URL:', encryptedUrl);

        // Prova prima con HEAD
        GM.xmlHttpRequest({
            method: 'HEAD',
            url: encryptedUrl,
            onload: (response) => {
                let finalUrl = response.finalUrl || encryptedUrl;
                finalUrl = transformUrl(finalUrl);

                if (response.finalUrl && response.finalUrl !== encryptedUrl) {
                    console.log(`[Bypass] Redirect a: ${finalUrl}`);
                    addLinkToUI(finalUrl, false, 'online');
                } else {
                    console.log(`[Bypass] Link online: ${finalUrl}`);
                    verifyWithGetRequest(encryptedUrl, linkStatus);
                    return;
                }
                onLinkProcessed();
            },
            onerror: (error) => {
                console.log(`[Bypass] HEAD fallito, provo con GET: ${encryptedUrl}`, error);
                verifyWithGetRequest(encryptedUrl, linkStatus);
            }
        });
    }

    function verifyWithGetRequest(url, linkStatus = 'unknown') {
        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            onload: (response) => {
                let finalUrl = response.finalUrl || url;
                finalUrl = transformUrl(finalUrl);

                if (response.responseText) {
                    const jsRedirect = extractLinkFromScriptsFromText(response.responseText);
                    if (jsRedirect) {
                        console.log(`[Bypass] Redirect JS trovato: ${jsRedirect}`);
                        finalUrl = jsRedirect;
                    }
                }

                console.log(`[Bypass] Link risolto con GET: ${finalUrl}`);
                addLinkToUI(finalUrl, false, 'online');
                onLinkProcessed();
            },
            onerror: (error) => {
                console.log(`[Bypass] GET fallito per: ${url}`, error);
                // Non mostrare come errore, ma come unknown
                addLinkToUI(url, false, linkStatus);
                onLinkProcessed();
            }
        });
    }

    function extractLinkFromScriptsFromText(html) {
        if (!html) return null;

        const patterns = [
            /top\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /window\.location\.href\s*=\s*['"]([^'"]+)['"]/,
            /location\.href\s*=\s*['"]([^'"]+)['"]/,
            /window\.open\(['"]([^'"]+)['"]/,
            /window\.location\.replace\(['"]([^'"]+)['"]/
        ];

        for (const pattern of patterns) {
            const match = html.match(pattern);
            if (match && match[1]) {
                return match[1];
            }
        }
        return null;
    }

    function transformUrl(url) {
        if (url.includes('terabytez.org/login?redirect=')) {
            return url.replace('/login?redirect=', '/');
        }
        return url;
    }

    function onLinkProcessed() {
        pendingCount--;

        if (pendingCount > 0) {
            updateStatus(`Decrypting links... (${pendingCount} remaining)`);
        } else {
            if (totalLinksCount > 0) {
                updateStatus(`Done! ${totalLinksCount} link${totalLinksCount > 1 ? 's' : ''} decrypted.`);
            } else {
                updateStatus('No links could be decrypted.', true);
            }
        }
    }

    // ==================== LIVE UI UPDATES ====================

    function getHostFromUrl(url) {
        try {
            return new URL(url).hostname;
        } catch {
            return 'Unknown Host';
        }
    }

    function addLinkToUI(url, isError = false, linkStatus = 'unknown') {
        const host = getHostFromUrl(url);
        totalLinksCount++;

        if (!linkGroups.has(host)) {
            linkGroups.set(host, []);
        }
        linkGroups.get(host).push({ url, isError, linkStatus });

        if (!groupElements.has(host)) {
            const groupEl = createGroupElement(host);
            groupElements.set(host, groupEl);
            insertGroupSorted(groupEl, host);
        }

        const group = groupElements.get(host);
        const linksContainer = group.querySelector('.fc-bypass-links-container');
        const linkRow = createLinkRow(url, isError, linkStatus);
        linksContainer.appendChild(linkRow);

        updateGroupTitle(host);
    }

    function createGroupElement(host) {
        const group = document.createElement('DIV');
        group.className = 'fc-bypass-group';
        group.dataset.host = host;

        const groupHeader = document.createElement('DIV');
        groupHeader.className = 'fc-bypass-group-header';

        const title = document.createElement('SPAN');
        title.className = 'fc-bypass-group-title';
        title.textContent = host;
        groupHeader.appendChild(title);

        const copyGroupBtn = createButton('Copy Links', 'fc-bypass-btn', () => {
            const links = (linkGroups.get(host) || []).map(l => l.url);
            copyLinksToClipboard(links);
            showCopyFeedback(copyGroupBtn, 'Copied!');
        });
        groupHeader.appendChild(copyGroupBtn);

        group.appendChild(groupHeader);

        const linksContainer = document.createElement('DIV');
        linksContainer.className = 'fc-bypass-links-container';
        group.appendChild(linksContainer);

        return group;
    }

    function insertGroupSorted(groupEl, host) {
        const wrapper = document.getElementById('fc-bypass-groups');
        if (!wrapper) return;

        const existingGroups = Array.from(wrapper.children);
        let inserted = false;

        for (const existing of existingGroups) {
            const existingHost = existing.dataset.host;
            if (host.localeCompare(existingHost) < 0) {
                wrapper.insertBefore(groupEl, existing);
                inserted = true;
                break;
            }
        }

        if (!inserted) {
            wrapper.appendChild(groupEl);
        }
    }

    function updateGroupTitle(host) {
        const group = groupElements.get(host);
        if (!group) return;

        const title = group.querySelector('.fc-bypass-group-title');
        const count = linkGroups.get(host)?.length || 0;
        title.textContent = `${host} (${count})`;
    }

    function createLinkRow(url, isError = false, linkStatus = 'unknown') {
        const row = document.createElement('DIV');
        row.className = 'fc-bypass-link-row';

        const link = document.createElement('SPAN');
        link.className = 'fc-bypass-link';
        link.title = url;

        const actualStatus = (isError && linkStatus === 'online') ? 'online' : linkStatus;
        const actualError = isError && linkStatus !== 'online';

        if (actualError) {
            link.textContent = `${truncateUrl(url)} (error)`;
            link.style.color = 'red';
            link.style.cursor = 'default';
        } else if (actualStatus === 'offline') {
            link.textContent = truncateUrl(url);
            link.style.color = 'red';
            link.addEventListener('click', () => window.open(url, '_blank'));
        } else if (actualStatus === 'online') {
            link.textContent = truncateUrl(url);
            link.style.color = '#4CAF50';
            link.addEventListener('click', () => window.open(url, '_blank'));
        } else {
            link.textContent = truncateUrl(url);
            link.style.color = '#FFA500'; // Arancione per "da verificare"
            link.addEventListener('click', () => window.open(url, '_blank'));
        }

        row.appendChild(link);

        const copyBtn = createButton('Copy', 'fc-bypass-btn', () => {
            copyLinksToClipboard([url]);
            showCopyFeedback(copyBtn, 'Copied!');
        });
        row.appendChild(copyBtn);

        return row;
    }

    function createButton(text, className, onClick) {
        const btn = document.createElement('BUTTON');
        btn.className = className;
        btn.textContent = text;
        btn.addEventListener('click', onClick);
        return btn;
    }

    // ==================== UTILITY FUNCTIONS ====================

    function updateStatus(message, isError = false) {
        const status = document.getElementById('fc-bypass-status');
        if (status) {
            status.textContent = message;
            status.style.color = isError ? 'red' : (CONFIG.isDarkMode ? '#aaa' : '#666');
        }
    }

    function truncateUrl(url, maxLength = 40) {
        if (url.length <= maxLength) return url;

        const ellipsis = '...';
        const availableChars = maxLength - ellipsis.length;
        const startChars = Math.ceil(availableChars / 2);
        const endChars = Math.floor(availableChars / 2);

        return url.substring(0, startChars) + ellipsis + url.substring(url.length - endChars);
    }

    function copyLinksToClipboard(links) {
        const text = links.join('\n');
        navigator.clipboard.writeText(text);
    }

    function copyAllLinks() {
        const allLinks = [];
        const sortedHosts = Array.from(linkGroups.keys()).sort();

        for (const host of sortedHosts) {
            const links = linkGroups.get(host) || [];
            allLinks.push(...links.map(l => l.url));
        }

        if (allLinks.length === 0) {
            alert('No links to copy yet!');
            return;
        }

        copyLinksToClipboard(allLinks);

        const copyAllBtn = document.getElementById('fc-bypass-copy-all');
        if (copyAllBtn) {
            showCopyFeedback(copyAllBtn, `Copied ${allLinks.length}!`);
        }
    }

    function showCopyFeedback(button, message) {
        const originalText = button.textContent;
        button.textContent = message;
        button.disabled = true;

        setTimeout(() => {
            button.textContent = originalText;
            button.disabled = false;
        }, 1500);
    }

    // ==================== START ====================

    init();
})();