1337x - Combined Enhancements 2025 (v11) - Configurable

Adds a column with torrent and magnet links, extends titles, adds images, full width site with configurable settings

// ==UserScript==
// @name         1337x - Combined Enhancements 2025 (v11) - Configurable
// @namespace    http://tampermonkey.net/
// @version      2025.9
// @description  Adds a column with torrent and magnet links, extends titles, adds images, full width site with configurable settings
// @author       sharmanhall
// @contributor  darkred, NotNeo, barn852, French Bond
// @match        *://*.1337x.to/*
// @match        *://*.1337x.ws/*
// @match        *://*.1337x.eu/*
// @match        *://*.1337x.se/*
// @match        *://*.1337x.is/*
// @match        *://*.1337x.gd/*
// @match        *://*.x1337x.cc/*
// @match        https://www.1377x.to/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=1337x.to
// ==/UserScript==
//
//    Thanks to:
//    - French Bond: modified original script taken from https://greasyfork.org/en/scripts/479974-1337x-ux-enhancement
//    - darkred: modified original script taken from https://greasyfork.org/en/scripts/479974-1337x-ux-enhancement
//    - NotNeo: most of the CSS used is taken from this script: https://greasyfork.org/en/scripts/373230-1337x-magnet-torrent-links-everywhere .
//    - barn852 for his contribution here: https://greasyfork.org/en/scripts/420754-1337x-torrent-and-magnet-links/discussions/96026
//
// Official mirrors list: https://1337x.to/about
//
// @downloadURL https://update.greasyfork.org/scripts/483602/1337x%20-%20Combined%20Enhancements%202024.user.js
// @updateURL https://update.greasyfork.org/scripts/483602/1337x%20-%20Combined%20Enhancements%202024.meta.js
// ==/UserScript==

(function() {
    'use strict';

    // Configuration object
let config = {
    showThumbnails: GM_getValue('showThumbnails', true),
    showExtraColumn: GM_getValue('showExtraColumn', true), //shows column with magnet/torrent links
    fullWidthSite: GM_getValue('fullWidthSite', true),
    visibleImages: GM_getValue('visibleImages', 4),
    queueFetchDelay: GM_getValue('queueFetchDelay', 0), //default value is 0ms between fetch requests
    maxRetries: GM_getValue('maxRetries', 1) //default is 2 max retries per queue request
};
    let extraColumnAdded = false;

    // CSS for the settings menu
    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; /* Start off-screen */
    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;
}

#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;
}

/* Custom radio buttons */
.x1337-option input[type="radio"] {
    display: none;
}

.x1337-option input[type="radio"] + label:before {
    content: '';
    display: inline-block;
    width: 16px;
    height: 16px;
    margin-right: 10px;
    border: 2px solid #F14E13;
    border-radius: 50%;
    background-color: #2d2d2d;
}

.x1337-option input[type="radio"]:checked + label:before {
    background-color: #F14E13;
    box-shadow: inset 0 0 0 3px #2d2d2d;
}

/* Ensure text is always white for better readability */
#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;
    }
    `;

    // Add settings menu HTML
const settingsHTML = `
<script>
</script>
    <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="50" 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-full-width-site" ${config.fullWidthSite ? 'checked' : ''}>
                    Full Width Site
                </label>
            </div>
            <button id="x1337-save-settings">Save Settings</button><br>
            <small>v2025.9 | by sharmanhall</small>
        </div>

    </div>
`;

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 {
                // Find the correct link (the second one, which is the title link)
                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');
}

function handleDownloadClick(event) {
    event.preventDefault();
    const button = event.currentTarget;
    console.log('Clicked button:', button);

    let href = button.getAttribute('data-href');
    console.log('Data-href attribute:', href);

    if (!href) {
        console.error('No data-href attribute found on the button');
        showPopup('Error: No link found');
        return;
    }

    // Prepend the base URL if it's a relative URL
    if (href.startsWith('/')) {
        href = 'https://1337x.to' + href;
    }

    const ismagnet = button.classList.contains('list-button-magnet');
    console.log('Is magnet link:', ismagnet);

    //fetchContent(href, (doc) => {
    queuefetchContent(href, (doc) => {
        console.log('Fetched content, searching for link');
        showPopup('Fetched content, searching for link');
        let link = ismagnet ?
            doc.querySelector("a[href^='magnet:']") :
            doc.querySelector("a[href*='itorrents.org/torrent/']");

        if (link) {
            console.log('Found link:', link.href);
            showPopup('found link:', link.href);
            button.href = link.href;
            button.removeEventListener('click', handleDownloadClick);
            console.log('Triggering click on button');
            button.click();
        } else {
            console.error('Download link not found in fetched content');
            console.log('Fetched HTML:', doc.documentElement.outerHTML);
            showPopup('Download link not found');
        }
    }, 5000, 2); // Increased retry delay to 5 seconds and max retries to 2
}


    // Function to add click listeners for magnet and download links
    function addClickListeners(links, type) {
        links.forEach((link) => {
            link.addEventListener('click', function(){
                let href = this.getAttribute('href');
                if (href === 'javascript:void(0)') {
                    let tLink = this.getAttribute('data-href');

                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: tLink,
                        onload: function (response) {
                            let container = document.implementation.createHTMLDocument().documentElement;
                            container.innerHTML = response.responseText;

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

                            if (retrievedLink) {
                                link.setAttribute('href', retrievedLink.href.replace('http:', 'https:'));
                                link.click();
                            }
                        }
                    });
                }
            }, false);
        });
    }

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;

    // Check if thumbnails are already added
    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;

            // URL restructuring for larger resolutions
            if (imgSrc.includes('https://imgtraffic.com/1s/')) {
                imgSrc = imgSrc.replace('https://imgtraffic.com/1s/', 'https://imgtraffic.com/1/');
            }
            if (imgSrc.includes('https://pilot007.org/images/')) {
                imgSrc = imgSrc.replace(/.th\.jpg$/, '.jpg');
            }
            if (imgSrc.includes('https://13xpics.space/images/')) {
                imgSrc = imgSrc.replace(/.th\.jpg$/, '.jpg');
            }
            if (imgSrc.includes('https://37xpics.space/images/')) {
                imgSrc = imgSrc.replace(/.th\.jpg$/, '.jpg');
            }
            if (/https?:\/\/.*\/images\/.*\.th\.jpg$/.test(imgSrc)) {
                imgSrc = imgSrc.replace(/\.th\.jpg$/, '.jpg');
            }
            if (imgSrc.includes('https://22pixx.xyz/as/')) {
                imgSrc = imgSrc.replace('https://22pixx.xyz/as/', 'https://22pixx.xyz/a/');
            }
            if (imgSrc.includes('http://imgblaze.net/data_server_')) {
                imgSrc = imgSrc.replace('http://imgblaze.net/data_server_', 'https://www.imgopaleno.site/data_server_')
                               .replace('/small/small_', '/big/');
            }
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);

            // Add mouseover events to each image
            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);
    }
}
/*            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);

            // Add mouseover events to each image
            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 to show an enlarged image
/*function showEnlargedImg(imgSrc, event) {
    removeEnlargedImg(); // Remove any existing enlarged image
    const enlargedImg = document.createElement('img');
    enlargedImg.src = imgSrc;
    enlargedImg.id = 'x1337-enlarged-img';
    enlargedImg.style.position = 'fixed';
    enlargedImg.style.maxWidth = '500px';
    enlargedImg.style.maxHeight = '500px';
    enlargedImg.style.zIndex = '10000';
    enlargedImg.style.border = '2px solid #F14E13';
    enlargedImg.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
    document.body.appendChild(enlargedImg);
    updateEnlargedImgPosition(event);
}*/

// Function to show an enlarged image
function showEnlargedImg(imgSrc, event) {
    removeEnlargedImg(); // Remove any existing enlarged image
    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)';

    // Set max dimensions while maintaining aspect ratio
    enlargedImg.style.maxWidth = '500px'; // Set max width
    enlargedImg.style.maxHeight = '500px'; // Set max height
    enlargedImg.style.width = 'auto'; // Set width to auto to maintain aspect ratio
    enlargedImg.style.height = 'auto'; // Set height to auto to maintain aspect ratio

    // Append the enlarged image to the document
    document.body.appendChild(enlargedImg);
    updateEnlargedImgPosition(event);
}



// Function to update the position of the enlarged image
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; // 20px offset from cursor
        let top = e.clientY + 20;

        // Adjust position if it goes off-screen
        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 to remove enlarged image
function removeEnlargedImg() {
    const enlargedImg = document.getElementById('x1337-enlarged-img');
    if (enlargedImg) {
        document.body.removeChild(enlargedImg);
    }
}

    // Function to optimize image URL
    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 to inject custom CSS
    function injectCustomCSS() {
        let customCSS = `
            .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;
            }
        `;

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

        GM_addStyle(customCSS);
    }

    // Function to clean the page title
    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 to modify H1 content on torrent pages
    function modifyH1ContentOnTorrentPages() {
        if (window.location.pathname.startsWith('/torrent/')) {
            let h1Element = document.querySelector('.box-info-heading h1');
            if (h1Element) {
                h1Element.textContent = cleanTitle(document.title);
            }
        }
    }

// Queue for managing fetch requests
const fetchQueue = [];
let isProcessingQueue = false;

// Function to add a fetch request to the queue
/*function queueFetchRequest(link, onSuccess, retryDelay = 5000, maxRetries = 2) {
    fetchQueue.push({ link, onSuccess, retryDelay, maxRetries, retries: 0 });
    if (!isProcessingQueue) {
        processQueue();
    }
}*/
function queueFetchRequest(link, onSuccess) {
    fetchQueue.push({ link, onSuccess, retries: 0 });
    if (!isProcessingQueue) {
        processQueue();
    }
}

// Function to process the queue
/*function processQueue() {
    if (fetchQueue.length === 0) {
        isProcessingQueue = false;
        return;
    }

    isProcessingQueue = true;
    const { link, onSuccess, retryDelay, maxRetries, retries } = fetchQueue.shift();

    fetchContent(link, onSuccess, retryDelay, maxRetries, retries);
}*/

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

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

    fetchContent(link, onSuccess, retries);
}

// Modified fetchContent function with adjustable delay
/*function fetchContent(link, onSuccess, retries = 0) {
    console.log(`Attempting to fetch ${link} (Attempt ${retries + 1} of ${config.maxRetries + 1})`);

    GM_xmlhttpRequest({
        method: 'GET',
        url: link,
        onload: function(response) {
            console.log(`Received response for ${link} with status ${response.status}`);

            if (response.status === 200) {
                console.log(`Successfully fetched ${link}`);
                let parser = new DOMParser();
                let doc = parser.parseFromString(response.responseText, 'text/html');
                console.log(`Parsed HTML content for ${link}`);
                onSuccess(doc);
                setTimeout(processQueue, config.queueFetchDelay);
            } else if (response.status === 429 && retries < config.maxRetries) {
                console.warn(`Rate limited for ${link}. Retrying in ${config.queueFetchDelay * 10}ms (Retry ${retries + 1} of ${config.maxRetries})`);
                fetchQueue.push({ link, onSuccess, retries: retries + 1 });
                setTimeout(processQueue, config.queueFetchDelay * 10);
            } else {
                console.error(`Failed to fetch ${link} after ${retries} retries. Status: ${response.status}`);
                console.error(`Response text: ${response.responseText}`);
                showPopup(`Failed to fetch ${link} after ${retries} retries`);
                setTimeout(processQueue, config.queueFetchDelay);
            }
        },
        onerror: function(error) {
            console.error(`Error fetching ${link}:`, error);
            console.error(`Error details:`, JSON.stringify(error));
            setTimeout(processQueue, config.queueFetchDelay);
        },
        ontimeout: function() {
            console.error(`Request timed out for ${link}`);
            setTimeout(processQueue, config.queueFetchDelay);
        }
    });
}*/
function fetchContent(link, onSuccess, retries = 0) {
    console.log(`Attempting to fetch ${link} (Attempt ${retries + 1} of ${config.maxRetries + 1})`);

    GM_xmlhttpRequest({
        method: 'GET',
        url: link,
        onload: function(response) {
            console.log(`Received response for ${link} with status ${response.status}`);

            if (response.status === 200) {
                console.log(`Successfully fetched ${link}`);
                let parser = new DOMParser();
                let doc = parser.parseFromString(response.responseText, 'text/html');
                console.log(`Parsed HTML content for ${link}`);
                onSuccess(doc);
                setTimeout(processQueue, config.queueFetchDelay);
            } else if (response.status === 429 && retries < config.maxRetries) {
                console.warn(`Rate limited for ${link}. Retrying in ${config.queueFetchDelay * 10}ms (Retry ${retries + 1} of ${config.maxRetries})`);
                fetchQueue.push({ link, onSuccess, retries: retries + 1 });
                setTimeout(processQueue, config.queueFetchDelay * 10);
            } else {
                console.error(`Failed to fetch ${link} after ${retries} retries. Status: ${response.status}`);
                console.error(`Response text: ${response.responseText}`);
                showPopup(`Failed to fetch ${link} after ${retries} retries`);
                setTimeout(processQueue, config.queueFetchDelay);
            }
        },
        onerror: function(error) {
            console.error(`Error fetching ${link}:`, error);
            console.error(`Error details:`, JSON.stringify(error));
            setTimeout(processQueue, config.queueFetchDelay);
        },
        ontimeout: function() {
            console.error(`Request timed out for ${link}`);
            setTimeout(processQueue, config.queueFetchDelay);
        }
    });
}

    // Function to process a link
function processLink(link) {
    // Find the parent row of the link
    const row = link.closest('tr');

     // Check if the row has already been processed
    if (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);
        }
        addDownloadButtons(link, torrentLink, magnetLink);

        // Mark the row as processed
        row.dataset.processed = 'true';
    });
}

    // Function to update the link title
    function updateLinkTitle(link, doc) {
        let title = cleanTitle(doc.querySelector('title').innerText);
        link.innerText = title;
    }

    // Function to add download buttons next to the link
function addDownloadButtons(link, torrentLink, magnetLink) {
    // Check if buttons are already added
    let existingTorrentButton = link.parentNode.querySelector('#DLT');
    let existingMagnetButton = link.parentNode.querySelector('#DLM');

    // Create a container for buttons if not already present
    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);
    }

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

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


    // Function to update download buttons
    function updateDownloadButtons() {
        document.querySelectorAll('.table-list-wrap .table-list tbody tr').forEach(row => {
            let torrentPageLink = row.querySelector('.coll-1.name a').getAttribute('href');
            let fullTorrentPageLink = `https://1337x.to${torrentPageLink}`;

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

                let dlButtonsCell = row.querySelector('.coll-1b.dl-buttons');
                if (dlButtonsCell) {
                    updateOrCreateButtons(dlButtonsCell, torrentLink, magnetLink);
                }
            });
        });
    }

    // Function to update or create buttons
    function updateOrCreateButtons(dlButtonsCell, torrentLink, magnetLink) {
        let existingButtons = dlButtonsCell.querySelectorAll('a');
        if (existingButtons.length === 2) {
            if (torrentLink) existingButtons[0].href = torrentLink.href.replace('http:', 'https:');
            if (magnetLink) existingButtons[1].href = magnetLink.href;
        } else {
            dlButtonsCell.innerHTML = createButtonHTML(torrentLink, magnetLink);
        }
    }

    // Function to create HTML for download and magnet buttons
    function createButtonHTML(torrentLink, magnetLink) {
        let torrentButtonHTML = torrentLink ? `<a href="${torrentLink.href.replace('http:', 'https:')}" title="Download torrent file"><i class="flaticon-torrent-download" style="color: #89ad19; font-size: 16px"></i></a>` : '';
        let magnetButtonHTML = magnetLink ? `<a href="${magnetLink.href}" title="Download via magnet"><i class="flaticon-magnet" style="color: #da3a04; font-size: 16px"></i></a>` : '';
        return `<div style="display: flex; align-items: center; gap: 5px; margin-top: 10px;">${torrentButtonHTML}${magnetButtonHTML}</div>`;
    }

    // Function to replace link text with titles and append images
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);
        }
    });
}

/*function showPopup(message, duration = 10000) {
    const popup = document.createElement('div');
    popup.id = 'x1337-popup';
    popup.textContent = message;
    document.body.appendChild(popup);

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

    // Fade out and remove
    setTimeout(() => {
        popup.style.opacity = '0';
        setTimeout(() => {
            document.body.removeChild(popup);
        }, 300);
    }, duration);
}*/

let popupQueue = [];
let isShowingPopup = false;

function showPopup(message, duration = 10000) {
    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);

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

    // Fade out and remove
    setTimeout(() => {
        popup.style.opacity = '0';
        setTimeout(() => {
            document.body.removeChild(popup);
            displayNextPopup(); // Show next popup in queue
        }, 300);
    }, duration);
}

// Save settings
function addSettingsMenu() {

    GM_addStyle(settingsCSS);
    document.body.insertAdjacentHTML('beforeend', settingsHTML);

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

    // Add event listener for showing/hiding the visible images input
    document.getElementById('x1337-show-thumbnails').addEventListener('change', function() {
        const visibleImagesOption = document.querySelector('.x1337-sub-option');
        visibleImagesOption.style.display = this.checked ? 'block' : 'none';
    })

    // Save settings
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.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('fullWidthSite', config.fullWidthSite);
    GM_setValue('visibleImages', config.visibleImages);
    GM_setValue('queueFetchDelay', config.queueFetchDelay);
    GM_setValue('maxRetries', config.maxRetries);

    // Apply changes immediately
    applySettings();

    // Show the popup
    showPopup('Settings saved successfully!');
    //console.error('Settings saved successfully!');

});
}


function applySettings() {
    if (config.showExtraColumn) {
        appendColumn();
    } else {
        // Remove extra column if it exists, including the header
        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; // Reset the flag
    }

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

    // Re-apply thumbnails settings and update titles and download buttons
    replaceLinkTextWithTitlesAndAppendImages();

    // Update CSS for full width if needed
    injectCustomCSS();
}

// Add this near the end of your script, just before the init() call
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('.table-list tbody tr')) {
                        let link = node.querySelector('a[href^="/torrent/"]');
                        if (link) {
                            processLink(link);
                        }
                    }
                });
            }
        });
    });

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


let hasInitialized = false;

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

    showPopup("1337x Enhancements v11 (by sharmanhall)", 5000);
    addSettingsMenu();
    injectCustomCSS();
    applySettings();
    modifyH1ContentOnTorrentPages();
    replaceLinkTextWithTitlesAndAppendImages();

    // Add mutation observer to handle dynamically loaded content
    addMutationObserver();
}

// Run the script
init();

})();