1337x - Combined Enhancements 2025 (v16) - Configurable

Adds a column with torrent and magnet links, extends titles, adds images, full width site with configurable settings. Uses native fetch for Cloudflare compatibility.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         1337x - Combined Enhancements 2025 (v16) - Configurable
// @namespace    http://tampermonkey.net/
// @version      2025.16
// @description  Adds a column with torrent and magnet links, extends titles, adds images, full width site with configurable settings. Uses native fetch for Cloudflare compatibility.
// @author       sharmanhall
// @contributor  darkred, NotNeo, barn852, French Bond

// @match        *://1337x.to/*
// @match        *://1337x.st/*
// @match        *://x1337x.cc/*
// @match        *://x1337x.ws/*
// @match        *://x1337x.eu/*
// @match        *://x1337x.se/*
// @include      http://l337xdarkkaqfwzntnfk5bmoaroivtl6xsbatabvlb52umg6v3ch44yd.onion/*

// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=1337x.to
// ==/UserScript==

//    v16 Changes:
//    - FIXED: Cloudflare 403 errors by using native fetch() instead of GM_xmlhttpRequest
//    - Native fetch properly sends HttpOnly cookies (like cf_clearance) that GM_xmlhttpRequest cannot access
//    - Removed GM_xmlhttpRequest dependency entirely for fetching torrent pages
//
//    Thanks to:
//    - French Bond, darkred, NotNeo, barn852 for original scripts

(function() {
    'use strict';

    // Configuration object
    let config = {
        showThumbnails: GM_getValue('showThumbnails', true),
        showExtraColumn: GM_getValue('showExtraColumn', true),
        showButtonsInNameColumn: GM_getValue('showButtonsInNameColumn', false),
        fullWidthSite: GM_getValue('fullWidthSite', true),
        visibleImages: GM_getValue('visibleImages', 4),
        queueFetchDelay: GM_getValue('queueFetchDelay', 100),
        maxRetries: GM_getValue('maxRetries', 3)
    };

    let extraColumnAdded = false;

    // CSS for the settings menu and buttons
    const settingsCSS = `
        .list-button-magnet > i.flaticon-magnet {
            font-size: 13px;
            color: #da3a04
        }
        .list-button-dl > i.flaticon-torrent-download {
            font-size: 13px;
            color: #89ad19;
        }
        table.table-list td.dl-buttons {
            border-left: 1px solid #f6f6f6;
            border-right: 1px solid #c0c0c0;
            padding-left: 2.5px;
            padding-right: 2.5px;
            text-align: center !important;
            position: relative;
            display: table-cell !important;
            width: 6%;
        }
        td.dl-buttons > a,
        td.dl-buttons > a:hover,
        td.dl-buttons > a:visited,
        td.dl-buttons > a:link,
        td.dl-buttons > a:active {
            color: inherit;
            text-decoration: none;
            cursor: pointer;
            display: inline-block !important;
            margin: 0 2px;
        }
        table.table-list td.coll-1b {
            border-right: 1px solid silver;
        }
        .table-list > thead > tr > th:nth-child(2),
        .table-list > thead > tr > td:nth-child(2) {
            text-align: center;
        }
        #x1337-settings-wrapper {
            font-family: 'Open Sans', sans-serif;
            background-color: #1d1d1d;
            color: #fff;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(241, 78, 19, 0.5);
            position: fixed;
            top: 20px;
            right: -300px;
            width: 300px;
            transition: right 0.3s ease;
            z-index: 9999;
        }
        #x1337-settings-toggle {
            position: absolute;
            left: -30px;
            top: 0;
            width: 30px;
            height: 30px;
            background-color: #F14E13;
            border-top-left-radius: 5px;
            border-bottom-left-radius: 5px;
            box-shadow: -2px 0 5px rgba(0,0,0,0.2);
            cursor: pointer;
            text-align: center;
            line-height: 30px;
        }
        #x1337-settings-content {
            padding: 15px;
        }
        #x1337-settings-content h3 {
            color: #F14E13;
            border-bottom: 1px solid #F14E13;
            padding-bottom: 10px;
            margin-bottom: 15px;
        }
        .x1337-option {
            margin-bottom: 15px;
            color: #fff;
        }
        .x1337-option label {
            display: flex;
            align-items: center;
            cursor: pointer;
            color: #fff;
        }
        .x1337-option input[type="checkbox"] {
            margin-right: 10px;
        }
        .x1337-option input[type="number"],
        .x1337-option input[type="text"] {
            background-color: #2d2d2d;
            border: 1px solid #F14E13;
            color: #fff;
            padding: 5px;
            border-radius: 3px;
            width: 80px;
        }
        #x1337-save-settings {
            background-color: #F14E13;
            color: #fff;
            border: none;
            padding: 10px 20px;
            border-radius: 3px;
            cursor: pointer;
            transition: background-color 0.3s;
            box-shadow: 0 0 10px rgba(241, 78, 19, 0.5);
        }
        #x1337-save-settings:hover {
            background-color: #ff6a3c;
        }
        #x1337-settings-wrapper,
        #x1337-settings-wrapper * {
            color: #fff;
        }
        #x1337-popup {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: #F14E13;
            color: #fff;
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 10000;
            opacity: 0;
            transition: opacity 0.3s ease-in-out;
        }
        .x1337-sub-option {
            margin-left: 25px;
            margin-top: 10px;
        }
        .x1337-sub-option label {
            display: block;
            margin-bottom: 8px;
            font-size: 12px;
        }
    `;

    const settingsHTML = `
        <div id="x1337-settings-wrapper">
            <div id="x1337-settings-toggle">⚙️</div>
            <div id="x1337-settings-content">
                <h3>1337x Enhancements Settings</h3>

                <div class="x1337-option">
                    <label>
                        <input type="checkbox" id="x1337-show-thumbnails" ${config.showThumbnails ? 'checked' : ''}>
                        Show Thumbnails
                    </label>
                    <div class="x1337-sub-option" ${config.showThumbnails ? '' : 'style="display:none;"'}>
                        <label>
                            Visible Images:
                            <input type="number" id="x1337-visible-images" value="${config.visibleImages}" min="1" max="10">
                        </label>
                        <label>
                            Queue Fetch Delay (ms):
                            <input type="number" id="x1337-queue-fetch-delay" value="${config.queueFetchDelay}" min="0" max="5000">
                        </label>
                        <label>
                            Max Fetch Retries:
                            <input type="number" id="x1337-max-retries" value="${config.maxRetries}" min="0" max="10">
                        </label>
                    </div>
                </div>
                <div class="x1337-option">
                    <label>
                        <input type="checkbox" id="x1337-show-magnet-column" ${config.showExtraColumn ? 'checked' : ''}>
                        Show Magnet URL Column
                    </label>
                </div>
                <div class="x1337-option">
                    <label>
                        <input type="checkbox" id="x1337-show-buttons-in-name" ${config.showButtonsInNameColumn ? 'checked' : ''}>
                        Show Buttons in Name Column
                    </label>
                </div>
                <div class="x1337-option">
                    <label>
                        <input type="checkbox" id="x1337-full-width-site" ${config.fullWidthSite ? 'checked' : ''}>
                        Full Width Site
                    </label>
                </div>
                <button id="x1337-save-settings">Save Settings</button><br>
                <small>v2025.16 | by sharmanhall</small>
            </div>
        </div>
    `;

    // ==================== FETCH QUEUE SYSTEM ====================
    const fetchQueue = [];
    let isProcessingQueue = false;

    function queueFetchRequest(url, onSuccess) {
        fetchQueue.push({ url, onSuccess, retries: 0 });
        if (!isProcessingQueue) {
            processQueue();
        }
    }

    function processQueue() {
        if (fetchQueue.length === 0) {
            isProcessingQueue = false;
            return;
        }

        isProcessingQueue = true;
        const { url, onSuccess, retries } = fetchQueue.shift();
        fetchContent(url, onSuccess, retries);
    }

    // Main fetch function using native fetch() - this properly sends HttpOnly cookies!
    function fetchContent(url, onSuccess, retries = 0) {
        fetch(url, {
            method: 'GET',
            credentials: 'same-origin', // This sends cookies including HttpOnly cf_clearance
            headers: {
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
            }
        })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            return response.text();
        })
        .then(text => {
            // Check if we got a Cloudflare challenge page
            const isCloudflare = text.includes('Just a moment') ||
                                text.includes('cf_chl') ||
                                text.includes('challenge-platform');

            if (isCloudflare) {
                if (retries < config.maxRetries) {
                    // Retry with exponential backoff
                    const delay = config.queueFetchDelay * Math.pow(2, retries + 1);
                    console.warn(`[1337x] Cloudflare challenge for ${url}, retrying in ${delay}ms`);
                    fetchQueue.push({ url, onSuccess, retries: retries + 1 });
                    setTimeout(processQueue, delay);
                } else {
                    console.error(`[1337x] Max retries reached for ${url}`);
                    setTimeout(processQueue, config.queueFetchDelay);
                }
                return;
            }

            // Success! Parse the HTML and call the callback
            const parser = new DOMParser();
            const doc = parser.parseFromString(text, 'text/html');
            onSuccess(doc);
            setTimeout(processQueue, config.queueFetchDelay);
        })
        .catch(error => {
            console.error(`[1337x] Fetch error for ${url}:`, error);

            if (retries < config.maxRetries) {
                const delay = config.queueFetchDelay * Math.pow(2, retries + 1);
                fetchQueue.push({ url, onSuccess, retries: retries + 1 });
                setTimeout(processQueue, delay);
            } else {
                setTimeout(processQueue, config.queueFetchDelay);
            }
        });
    }

    // ==================== COLUMN AND BUTTON FUNCTIONS ====================

    function appendColumn() {
        if (!config.showExtraColumn || extraColumnAdded) return;

        const allTables = document.querySelectorAll('.table-list-wrap');
        const isSeries = window.location.href.includes('/series/');
        const title = 'ml&nbsp;dl';

        allTables.forEach((table) => {
            const headersCellsInitial = table.querySelectorAll(`.table-list > thead > tr:not(.blank) > th:nth-child(1),
                                                                .table-list > tbody > tr:not(.blank) > td:nth-child(1)`);
            headersCellsInitial.forEach((cell, index) => {
                if (index === 0 && !isSeries) {
                    cell.insertAdjacentHTML('afterend', `<th class="coll-1b">${title}</th>`);
                } else {
                    let titleLink = cell.querySelectorAll('a')[1];
                    let href = titleLink ? titleLink.href : '';

                    cell.insertAdjacentHTML('afterend', `
                        <td class="coll-1b dl-buttons">
                            <a class="list-button-magnet" href="javascript:void(0)" data-href="${href}" title="Magnet link">
                                <i class="flaticon-magnet"></i>
                            </a>
                            <a class="list-button-dl" href="javascript:void(0)" data-href="${href}" title="Torrent download">
                                <i class="flaticon-torrent-download"></i>
                            </a>
                        </td>
                    `);
                }
            });
            extraColumnAdded = true;
        });

        addClickListeners(document.querySelectorAll('.list-button-magnet'), 'ml');
        addClickListeners(document.querySelectorAll('.list-button-dl'), 'dl');
    }

    // Click handler for magnet/download buttons - uses native fetch
    function addClickListeners(links, type) {
        links.forEach((link) => {
            link.addEventListener('click', function(e) {
                e.preventDefault();
                let href = this.getAttribute('href');

                if (href === 'javascript:void(0)') {
                    let tLink = this.getAttribute('data-href');
                    const button = this;

                    // Use native fetch to get the torrent page
                    fetch(tLink, { credentials: 'same-origin' })
                    .then(response => response.text())
                    .then(text => {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(text, 'text/html');

                        let retrievedLink = (type === 'ml')
                            ? doc.querySelector('a[href^="magnet:"]')
                            : doc.querySelector('.dropdown-menu > li > a');

                        if (retrievedLink) {
                            button.setAttribute('href', retrievedLink.href.replace('http:', 'https:'));
                            // Trigger the navigation
                            window.location.href = button.getAttribute('href');
                        } else {
                            showPopup('Link not found', 3000);
                        }
                    })
                    .catch(error => {
                        console.error('[1337x] Error fetching link:', error);
                        showPopup('Error fetching link', 3000);
                    });
                }
            }, false);
        });
    }

    // ==================== IMAGE/THUMBNAIL FUNCTIONS ====================

    function optimizeImageUrl(imgSrc) {
        const optimizations = [
            { from: 'https://imgtraffic.com/1s/', to: 'https://imgtraffic.com/1/' },
            { from: /https?:\/\/.*\/images\/.*\.th\.jpg$/, to: (url) => url.replace(/\.th\.jpg$/, '.jpg') },
            { from: 'https://22pixx.xyz/as/', to: 'https://22pixx.xyz/a/' },
            { from: 'http://imgblaze.net/data_server_', to: 'https://www.imgopaleno.site/data_server_' },
            { from: '/small/small_', to: '/big/' }
        ];

        return optimizations.reduce((url, opt) => {
            if (typeof opt.from === 'string') {
                return url.replace(opt.from, opt.to);
            } else if (opt.from instanceof RegExp) {
                return opt.from.test(url) ? url.replace(opt.from, opt.to) : url;
            }
            return url;
        }, imgSrc);
    }

    function appendImages(link, doc) {
        if (!config.showThumbnails) return;

        if (link.parentNode.querySelector('.thumbnail-container')) {
            return;
        }

        let images = doc.querySelectorAll('#description img');
        if (images.length > 0) {
            let flexContainer = document.createElement('div');
            flexContainer.classList.add('thumbnail-container');
            flexContainer.style.display = 'flex';
            flexContainer.style.flexWrap = 'wrap';
            flexContainer.style.gap = '10px';
            flexContainer.style.marginTop = '10px';
            let clonedImages = [];

            images.forEach((img, index) => {
                let clonedImg = img.cloneNode(true);
                let imgSrc = img.getAttribute('data-original') || img.src;
                imgSrc = optimizeImageUrl(imgSrc);

                clonedImg.src = imgSrc;
                clonedImg.style.maxHeight = '100px';
                clonedImg.style.setProperty('margin', '0', 'important');
                clonedImg.style.display = index < config.visibleImages ? 'block' : 'none';
                flexContainer.appendChild(clonedImg);
                clonedImages.push(clonedImg);

                clonedImg.addEventListener('mouseover', function(e) {
                    showEnlargedImg(imgSrc, e);
                });
                clonedImg.addEventListener('mousemove', updateEnlargedImgPosition);
                clonedImg.addEventListener('mouseout', removeEnlargedImg);
            });

            if (images.length > config.visibleImages) {
                let showMoreButton = document.createElement('button');
                showMoreButton.textContent = 'Show More';
                showMoreButton.onclick = function () {
                    let isShowingMore = showMoreButton.textContent === 'Show Less';
                    clonedImages.forEach((img, index) => {
                        if (index >= config.visibleImages) {
                            img.style.display = isShowingMore ? 'none' : 'block';
                        }
                    });
                    showMoreButton.textContent = isShowingMore ? 'Show More' : 'Show Less';
                };
                flexContainer.appendChild(showMoreButton);
            }

            link.parentNode.insertBefore(flexContainer, link.nextSibling);
        }
    }

    function showEnlargedImg(imgSrc, event) {
        removeEnlargedImg();
        const enlargedImg = document.createElement('img');
        enlargedImg.src = imgSrc;
        enlargedImg.id = 'x1337-enlarged-img';
        enlargedImg.style.position = 'fixed';
        enlargedImg.style.zIndex = '10000';
        enlargedImg.style.border = '2px solid #F14E13';
        enlargedImg.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        enlargedImg.style.maxWidth = '500px';
        enlargedImg.style.maxHeight = '500px';
        enlargedImg.style.width = 'auto';
        enlargedImg.style.height = 'auto';
        document.body.appendChild(enlargedImg);
        updateEnlargedImgPosition(event);
    }

    function updateEnlargedImgPosition(e) {
        const enlargedImg = document.getElementById('x1337-enlarged-img');
        if (enlargedImg) {
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;
            const imgWidth = enlargedImg.offsetWidth;
            const imgHeight = enlargedImg.offsetHeight;

            let left = e.clientX + 20;
            let top = e.clientY + 20;

            if (left + imgWidth > viewportWidth) {
                left = e.clientX - imgWidth - 20;
            }
            if (top + imgHeight > viewportHeight) {
                top = e.clientY - imgHeight - 20;
            }

            enlargedImg.style.left = `${left}px`;
            enlargedImg.style.top = `${top}px`;
        }
    }

    function removeEnlargedImg() {
        const enlargedImg = document.getElementById('x1337-enlarged-img');
        if (enlargedImg) {
            document.body.removeChild(enlargedImg);
        }
    }

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

    function injectCustomCSS() {
        let customCSS = settingsCSS;

        if (config.fullWidthSite) {
            customCSS += `
                .container { max-width: none !important; }
                main.container, div.container { max-width: 1450px; }
            `;
        }

        GM_addStyle(customCSS);
    }

    function cleanTitle(title) {
        if (title.startsWith('Download ')) {
            title = title.substring('Download '.length);
        }
        let pipeIndex = title.indexOf(' Torrent |');
        if (pipeIndex !== -1) {
            title = title.substring(0, pipeIndex);
        }
        return title;
    }

    function modifyH1ContentOnTorrentPages() {
        if (window.location.pathname.startsWith('/torrent/')) {
            let h1Element = document.querySelector('.box-info-heading h1');
            if (h1Element) {
                h1Element.textContent = cleanTitle(document.title);
            }
        }
    }

    // ==================== LINK PROCESSING ====================

    function processLink(link) {
        const row = link.closest('tr');
        if (row && row.dataset.processed === 'true') {
            return;
        }

        queueFetchRequest(link.href, (doc) => {
            let torrentLink = doc.querySelector("a[href*='itorrents.org/torrent/']");
            let magnetLink = doc.querySelector("a[href^='magnet:?']");

            updateLinkTitle(link, doc);

            if (config.showThumbnails) {
                appendImages(link, doc);
            }

            if (config.showButtonsInNameColumn) {
                addDownloadButtons(link, torrentLink, magnetLink);
            }

            if (row) {
                row.dataset.processed = 'true';
            }
        });
    }

    function updateLinkTitle(link, doc) {
        const titleEl = doc.querySelector('title');
        if (titleEl) {
            let title = cleanTitle(titleEl.innerText);
            link.innerText = title;
        }
    }

    function addDownloadButtons(link, torrentLink, magnetLink) {
        let existingTorrentButton = link.parentNode.querySelector('#DLT');
        let existingMagnetButton = link.parentNode.querySelector('#DLM');

        let buttonsContainer = link.parentNode.querySelector('.buttons-container');
        if (!buttonsContainer) {
            buttonsContainer = document.createElement('div');
            buttonsContainer.classList.add('buttons-container');
            buttonsContainer.style.display = 'flex';
            buttonsContainer.style.alignItems = 'center';
            buttonsContainer.style.gap = '5px';
            buttonsContainer.style.marginTop = '10px';
            link.after(buttonsContainer);
        }

        if (torrentLink && !existingTorrentButton) {
            let torrentButton = document.createElement('a');
            torrentButton.href = torrentLink.href.replace('http:', 'https:');
            torrentButton.title = 'Download torrent file';
            torrentButton.id = 'DLT';
            torrentButton.innerHTML = '<i class="flaticon-torrent-download" style="color: #89ad19; font-size: 16px"></i>';
            buttonsContainer.appendChild(torrentButton);
        }

        if (magnetLink && !existingMagnetButton) {
            let magnetButton = document.createElement('a');
            magnetButton.href = magnetLink.href;
            magnetButton.title = 'Download via magnet';
            magnetButton.id = 'DLM';
            magnetButton.innerHTML = '<i class="flaticon-magnet" style="color: #da3a04; font-size: 16px"></i>';
            buttonsContainer.appendChild(magnetButton);
        }
    }

    function replaceLinkTextWithTitlesAndAppendImages() {
        let unprocessedRows = document.querySelectorAll('.table-list tbody tr:not([data-processed])');
        unprocessedRows.forEach(row => {
            let link = row.querySelector('a[href^="/torrent/"]');
            if (link) {
                processLink(link);
            }
        });
    }

    // ==================== POPUP SYSTEM ====================

    let popupQueue = [];
    let isShowingPopup = false;

    function showPopup(message, duration = 5000) {
        popupQueue.push({ message, duration });
        if (!isShowingPopup) {
            displayNextPopup();
        }
    }

    function displayNextPopup() {
        if (popupQueue.length === 0) {
            isShowingPopup = false;
            return;
        }

        isShowingPopup = true;
        const { message, duration } = popupQueue.shift();

        const popup = document.createElement('div');
        popup.id = 'x1337-popup';
        popup.textContent = message;

        document.body.appendChild(popup);

        setTimeout(() => {
            popup.style.opacity = '1';
        }, 10);

        setTimeout(() => {
            popup.style.opacity = '0';
            setTimeout(() => {
                if (popup.parentNode) {
                    document.body.removeChild(popup);
                }
                displayNextPopup();
            }, 300);
        }, duration);
    }

    // ==================== SETTINGS MENU ====================

    function addSettingsMenu() {
        document.body.insertAdjacentHTML('beforeend', settingsHTML);

        document.getElementById('x1337-settings-toggle').addEventListener('click', function() {
            const wrapper = document.getElementById('x1337-settings-wrapper');
            wrapper.style.right = wrapper.style.right === '0px' ? '-300px' : '0px';
        });

        document.getElementById('x1337-show-thumbnails').addEventListener('change', function() {
            const visibleImagesOption = document.querySelector('.x1337-sub-option');
            visibleImagesOption.style.display = this.checked ? 'block' : 'none';
        });

        document.getElementById('x1337-save-settings').addEventListener('click', function() {
            config.showThumbnails = document.getElementById('x1337-show-thumbnails').checked;
            config.showExtraColumn = document.getElementById('x1337-show-magnet-column').checked;
            config.showButtonsInNameColumn = document.getElementById('x1337-show-buttons-in-name').checked;
            config.fullWidthSite = document.getElementById('x1337-full-width-site').checked;
            config.visibleImages = parseInt(document.getElementById('x1337-visible-images').value);
            config.queueFetchDelay = parseInt(document.getElementById('x1337-queue-fetch-delay').value);
            config.maxRetries = parseInt(document.getElementById('x1337-max-retries').value);

            GM_setValue('showThumbnails', config.showThumbnails);
            GM_setValue('showExtraColumn', config.showExtraColumn);
            GM_setValue('showButtonsInNameColumn', config.showButtonsInNameColumn);
            GM_setValue('fullWidthSite', config.fullWidthSite);
            GM_setValue('visibleImages', config.visibleImages);
            GM_setValue('queueFetchDelay', config.queueFetchDelay);
            GM_setValue('maxRetries', config.maxRetries);

            applySettings();
            showPopup('Settings saved successfully!');
        });
    }

    function applySettings() {
        if (config.showExtraColumn && !extraColumnAdded) {
            appendColumn();
        } else if (!config.showExtraColumn && extraColumnAdded) {
            document.querySelectorAll('.table-list-wrap').forEach(table => {
                const header = table.querySelector('.table-list > thead > tr > th.coll-1b');
                if (header) header.remove();

                table.querySelectorAll('.table-list > tbody > tr > td.coll-1b.dl-buttons').forEach(cell => {
                    cell.remove();
                });
            });
            extraColumnAdded = false;
        }

        if (!config.showButtonsInNameColumn) {
            document.querySelectorAll('.buttons-container').forEach(container => {
                container.remove();
            });
        }

        if (config.fullWidthSite) {
            document.body.classList.add('full-width-site');
        } else {
            document.body.classList.remove('full-width-site');
        }

        replaceLinkTextWithTitlesAndAppendImages();
        injectCustomCSS();
    }

    // ==================== MUTATION OBSERVER ====================

    function addMutationObserver() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches('.table-list tbody tr')) {
                            let link = node.querySelector('a[href^="/torrent/"]');
                            if (link) {
                                processLink(link);
                            }
                        }
                    });
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

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

    let hasInitialized = false;

    function init() {
        if (hasInitialized) return;
        hasInitialized = true;

        console.log('[1337x] Initializing v16 - Native Fetch Edition');

        injectCustomCSS();
        addSettingsMenu();
        applySettings();
        modifyH1ContentOnTorrentPages();
        replaceLinkTextWithTitlesAndAppendImages();
        addMutationObserver();

        showPopup("1337x Enhancements v16 (by sharmanhall)", 5000);
    }

    // Run the script
    init();

})();