1337x - Combined Enhancements 2025 (v13) - Configurable

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         1337x - Combined Enhancements 2025 (v13) - Configurable
// @namespace    http://tampermonkey.net/
// @version      2025.13
// @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

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

// @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
    showButtonsInNameColumn: GM_getValue('showButtonsInNameColumn', false), // new option, defaults to false
    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;
    }
    `;

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-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.13 | 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);
        }
        if (config.showButtonsInNameColumn) {
            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';
    })

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);

    // Apply changes immediately
    applySettings();

    // Show the popup
    showPopup('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.showButtonsInNameColumn) {
        // Remove buttons in name column if the setting is disabled
        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');
    }

    // 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 v13 (by sharmanhall)", 5000);
    addSettingsMenu();
    injectCustomCSS();
    applySettings();
    modifyH1ContentOnTorrentPages();
    replaceLinkTextWithTitlesAndAppendImages();

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

// Run the script
init();

})();