Nexus No Wait ++

Download from Nexusmods.com without wait and redirect (Manual/Vortex/MO2/NMM), Tweaked with extra features.

当前为 2025-02-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Nexus No Wait ++
// @description Download from Nexusmods.com without wait and redirect (Manual/Vortex/MO2/NMM), Tweaked with extra features.
// @namespace   NexusNoWaitPlusPlus
// @author      Torkelicious
// @version     1.1.6
// @include     https://*.nexusmods.com/*
// @run-at      document-idle
// @iconURL     https://raw.githubusercontent.com/torkelicious/nexus-no-wait-pp/refs/heads/main/icon.png
// @icon        https://raw.githubusercontent.com/torkelicious/nexus-no-wait-pp/refs/heads/main/icon.png
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @license MIT
// ==/UserScript==

/* global GM_getValue, GM_setValue, GM_deleteValue, GM_xmlhttpRequest, GM_info GM */

(function () {

    const DEFAULT_CONFIG = {
        autoCloseTab: true,        // Close tab after download starts
        skipRequirements: true,    // Skip requirements popup/tab
        showAlerts: true,          // Show errors as browser alerts
        refreshOnError: false,     // Refresh page on error
        requestTimeout: 30000,     // Request timeout (30 sec)
        closeTabTime: 1000,        // Wait before closing tab (1 sec)
        debug: false,              // Show debug messages as alerts
        playErrorSound: true,      // Play a sound on error
    };

    // === Configuration ===
    /**
     * @typedef {Object} Config
     * @property {boolean} autoCloseTab - Close tab after download starts
     * @property {boolean} skipRequirements - Skip requirements popup/tab
     * @property {boolean} showAlerts - Show errors as browser alerts
     * @property {boolean} refreshOnError - Refresh page on error
     * @property {number} requestTimeout - Request timeout in milliseconds
     * @property {number} closeTabTime - Wait before closing tab in milliseconds
     * @property {boolean} debug - Show debug messages as alerts
     * @property {boolean} playErrorSound - Play a sound on error
     */

    /**
     * @typedef {Object} SettingDefinition
     * @property {string} name - Display name of the setting
     * @property {string} description - Tooltip description
     */

    /**
     * @typedef {Object} UIStyles
     * @property {string} button - Button styles
     * @property {string} modal - Modal window styles
     * @property {string} settings - Settings header styles
     * @property {string} section - Section styles
     * @property {string} sectionHeader - Section header styles
     * @property {string} input - Input field styles
     * @property {Object} btn - Button variant styles
     */

    // === Settings Management ===
    /**
     * Validates settings object against default configuration
     * @param {Object} settings - Settings to validate
     * @returns {Config} Validated settings object
     */
    function validateSettings(settings) {
        if (!settings || typeof settings !== 'object') return {...DEFAULT_CONFIG};

        const validated = {...settings}; // Keep all existing settings

        // Settings validation
        for (const [key, defaultValue] of Object.entries(DEFAULT_CONFIG)) {
            if (typeof validated[key] !== typeof defaultValue) {
                validated[key] = defaultValue;
            }
        }

        return validated;
    }

    /**
     * Loads settings from storage with validation
     * @returns {Config} Loaded and validated settings
     */
    function loadSettings() {
        try {
            const saved = GM_getValue('nexusNoWaitConfig', null);
            const parsed = saved ? JSON.parse(saved) : DEFAULT_CONFIG;
            return validateSettings(parsed);
        } catch (error) {
            console.warn('GM storage load failed:', error);
            return {...DEFAULT_CONFIG};
        }
    }

    /**
     * Saves settings to storage
     * @param {Config} settings - Settings to save
     * @returns {void}
     */
    function saveSettings(settings) {
        try {
            GM_setValue('nexusNoWaitConfig', JSON.stringify(settings));
            logMessage('Settings saved to GM storage', false, true);
        } catch (e) {
            console.error('Failed to save settings:', e);
        }
    }
    const config = Object.assign({}, DEFAULT_CONFIG, loadSettings());

    // Create global sound instance

    const errorSound = new Audio('https://github.com/torkelicious/nexus-no-wait-pp/raw/refs/heads/main/errorsound.mp3');
    errorSound.load(); // Preload sound


     // Plays error sound if enabled

    function playErrorSound() {
        if (!config.playErrorSound) return;
        errorSound.play().catch(e => {
            console.warn("Error playing sound:", e);
        });
    }

    // === Error Handling ===

    /**
     * Centralized logging function
     * @param {string} message - Message to display/log
     * @param {boolean} [showAlert=false] - If true, shows browser alert
     * @param {boolean} [isDebug=false] - If true, handles debug logs
     * @returns {void}
     */
    function logMessage(message, showAlert = false, isDebug = false) {
        if (isDebug) {
            console.log("[Nexus No Wait ++]: " + message);
            if (config.debug) {
                alert("[Nexus No Wait ++] (Debug):\n" + message);
            }
            return;
        }

        playErrorSound();  // Play sound before alert
        console.error("[Nexus No Wait ++]: " + message);
        if (showAlert && config.showAlerts) {
            alert("[Nexus No Wait ++] \n" + message);
        }

        if (config.refreshOnError) {
            location.reload();
        }
    }

    // === URL and Navigation Handling ===
    /**
     * Auto-redirects from requirements to files
     */
    if (window.location.href.includes('tab=requirements') && config.skipRequirements)
    {
        const newUrl = window.location.href.replace('tab=requirements', 'tab=files');
        window.location.replace(newUrl);
        return;
    }

    // === AJAX Setup and Configuration ===
    let ajaxRequestRaw;
    if (typeof(GM_xmlhttpRequest) !== "undefined")
    {
        ajaxRequestRaw = GM_xmlhttpRequest;
    } else if (typeof(GM) !== "undefined" && typeof(GM.xmlHttpRequest) !== "undefined") {
        ajaxRequestRaw = GM.xmlHttpRequest;
    }

    // Wrapper for AJAX requests
    function ajaxRequest(obj) {
        if (!ajaxRequestRaw) {
            logMessage("AJAX functionality not available (Your browser or userscript manager may not support these requests!)", true);
            return;
        }
        ajaxRequestRaw({
            method: obj.type,
            url: obj.url,
            data: obj.data,
            headers: obj.headers,
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    obj.success(response.responseText);
                } else {
                    obj.error(response);
                }
            },
            onerror: function(response) {
                obj.error(response);
            },
            ontimeout: function(response) {
                obj.error(response);
            }
        });
    }

    // === Button Management ===

    /**
     * Updates button appearance and shows errors
     * @param {HTMLElement} button - The button element
     * @param {Error|Object} error - Error details
     */
    function btnError(button, error) {
        button.style.color = "red";
        let errorMessage = "Download failed: " + (error.message);
        button.innerText = "ERROR: " + errorMessage;
        logMessage(errorMessage, true);
    }

    function btnSuccess(button) {
        button.style.color = "green";
        button.innerText = "Downloading!";
        logMessage("Download started.", false, true);
    }

    function btnWait(button) {
        button.style.color = "yellow";
        button.innerText = "Wait...";
        logMessage("Loading...", false, true);
    }


    // Closes tab after download starts
    function closeOnDL()
    {
        if (config.autoCloseTab && !isArchiveDownload) // Modified to check for archive downloads
        {
        setTimeout(() => window.close(), config.closeTabTime);
        }
    }

    // === Download Handling ===
    /**
     * Main click event handler for download buttons
     * Handles both manual and mod manager downloads
     * @param {Event} event - Click event object
     */
    function clickListener(event) {
        // Skip if this is an archive download
        if (isArchiveDownload) {
            isArchiveDownload = false; // Reset the flag
            return;
        }

        const href = this.href || window.location.href;
        const params = new URL(href).searchParams;

        if (params.get("file_id")) {
            let button = event;
            if (this.href) {
                button = this;
                event.preventDefault();
            }
            btnWait(button);

            const section = document.getElementById("section");
            const gameId = section ? section.dataset.gameId : this.current_game_id;

            let fileId = params.get("file_id");
            if (!fileId) {
                fileId = params.get("id");
            }

            const ajaxOptions = {
                type: "POST",
                url: "/Core/Libs/Common/Managers/Downloads?GenerateDownloadUrl",
                data: "fid=" + fileId + "&game_id=" + gameId,
                headers: {
                    Origin: "https://www.nexusmods.com",
                    Referer: href,
                    "Sec-Fetch-Site": "same-origin",
                    "X-Requested-With": "XMLHttpRequest",
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
                },
                success(data) {
                    if (data) {
                        try {
                            data = JSON.parse(data);
                            if (data.url) {
                                btnSuccess(button);
                                document.location.href = data.url;
                                closeOnDL();
                            }
                        } catch (e) {
                            btnError(button, e);
                        }
                    }
                },
                error(xhr) {
                    btnError(button, xhr);
                }
            };

            if (!params.get("nmm")) {
                ajaxRequest(ajaxOptions);
            } else {
                ajaxRequest({
                    type: "GET",
                    url: href,
                    headers: {
                        Origin: "https://www.nexusmods.com",
                        Referer: document.location.href,
                        "Sec-Fetch-Site": "same-origin",
                        "X-Requested-With": "XMLHttpRequest"
                    },
                    success(data) {
                        if (data) {
                            const xml = new DOMParser().parseFromString(data, "text/html");
                            const slow = xml.getElementById("slowDownloadButton");
                            if (slow && slow.getAttribute("data-download-url")) {
                                const downloadUrl = slow.getAttribute("data-download-url");
                                btnSuccess(button);
                                document.location.href = downloadUrl;
                                closeOnDL();
                            } else {
                                btnError(button);
                            }
                        }
                    },
                    error(xhr) {
                        btnError(button, xhr);
                    }
                });
            }

            const popup = this.parentNode;
            if (popup && popup.classList.contains("popup")) {
                popup.getElementsByTagName("button")[0].click();
                const popupButton = document.getElementById("popup" + fileId);
                if (popupButton) {
                    btnSuccess(popupButton);
                    closeOnDL();
                }
            }
        } else if (/ModRequirementsPopUp/.test(href)) {
            const fileId = params.get("id");

            if (fileId) {
                this.setAttribute("id", "popup" + fileId);
            }
        }
    }

    // === Event Listeners  ===
    /**
     * Attaches click event listener with proper context
     * @param {HTMLElement} el - the element to attach listener to
     */
    function addClickListener(el) {
        el.addEventListener("click", clickListener, true);
    }

    // Attaches click event listeners to multiple elements
    function addClickListeners(els) {
        for (let i = 0; i < els.length; i++) {
            addClickListener(els[i]);
        }
    }

    // === Automatic Downloading ===
    function autoStartFileLink() {
        if (/file_id=/.test(window.location.href)) {
            clickListener(document.getElementById("slowDownloadButton"));
        }
    }

    // Automatically skips file requirements popup and downloads
    function autoClickRequiredFileDownload() {
        const observer = new MutationObserver(() => {
            const downloadButton = document.querySelector(".popup-mod-requirements a.btn");
            if (downloadButton) {
                downloadButton.click();
                const popup = document.querySelector(".popup-mod-requirements");
                if (!popup) {
                    logMessage("Popup closed", false, true);
                }
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['style', 'class']
        });
    }

    // === Archived Files Handling ===

    //SVG paths
    const ICON_PATHS = {
        nmm: 'https://www.nexusmods.com/assets/images/icons/icons.svg#icon-nmm',
        manual: 'https://www.nexusmods.com/assets/images/icons/icons.svg#icon-manual'
    };

    /**
     * Tracks if current download is from archives
     * @type {boolean}
     */
    let isArchiveDownload = false;

    function archivedFile() {
        try {
            // Only run in the archived category
            if (!window.location.href.includes('category=archived')) {
                return;
            }

            //  DOM queries and paths
            const path = `${location.protocol}//${location.host}${location.pathname}`;
            const downloadTemplate = (fileId) => `
                <li>
                    <a class="btn inline-flex download-btn"
                       href="${path}?tab=files&file_id=${fileId}&nmm=1"
                       data-fileid="${fileId}"
                       data-manager="true"
                       tabindex="0">
                        <svg title="" class="icon icon-nmm">
                            <use xlink:href="${ICON_PATHS.nmm}"></use>
                        </svg>
                        <span class="flex-label">Mod manager download</span>
                    </a>
                </li>
                <li>
                    <a class="btn inline-flex download-btn"
                       href="${path}?tab=files&file_id=${fileId}"
                       data-fileid="${fileId}"
                       data-manager="false"
                       tabindex="0">
                        <svg title="" class="icon icon-manual">
                            <use xlink:href="${ICON_PATHS.manual}"></use>
                        </svg>
                        <span class="flex-label">Manual download</span>
                    </a>
                </li>`;

            const downloadSections = Array.from(document.querySelectorAll('.accordion-downloads'));
            const fileHeaders = Array.from(document.querySelectorAll('.file-expander-header'));

            downloadSections.forEach((section, index) => {
                const fileId = fileHeaders[index]?.getAttribute('data-id');
                if (fileId) {
                    section.innerHTML = downloadTemplate(fileId);
                    const buttons = section.querySelectorAll('.download-btn');
                    buttons.forEach(btn => {
                        btn.addEventListener('click', function(e) {
                            e.preventDefault();
                            isArchiveDownload = true;
                            // Use existing download logic
                            clickListener.call(this, e);
                            // Reset flag after small delay
                            setTimeout(() => isArchiveDownload = false, 100);
                        });
                    });
                }
            });

        } catch (error) {
            logMessage('Error with archived file: ' + error.message, true);
            console.error('Archived file error:', error);
        }
    }
// --------------------------------------------- === UI === --------------------------------------------- //

    const SETTING_UI = {
        autoCloseTab: {
            name: 'Auto-Close tab on download',
            description: 'Automatically close tab after download starts'
        },
        skipRequirements: {
            name: 'Skip Requirements Popup/Tab',
            description: 'Skip requirements page and go straight to download'
        },
        showAlerts: {
            name: 'Show Error Alert messages',
            description: 'Show error messages as browser alerts'
        },
        refreshOnError: {
            name: 'Refresh page on error',
            description: 'Refresh the page when errors occur (may lead to infinite refresh loop!)'
        },
        requestTimeout: {
            name: 'Request Timeout',
            description: 'Time to wait for server response before timeout'
        },
        closeTabTime: {
            name: 'Auto-Close tab Delay',
            description: 'Delay before closing tab after download starts (Setting this too low may prevent download from starting!)'
        },
        debug: {
            name: "⚠️ Debug Alerts",
            description: "Show all console logs as alerts, don't enable unless you know what you are doing!"
        },
        playErrorSound: {
            name: 'Play Error Sound',
            description: 'Play a sound when errors occur'
        },
    };

    // Extract UI styles
    const STYLES = {
        button: `
            position:fixed;
            bottom:20px;
            right:20px;
            background:#2f2f2f;
            color:white;
            padding:10px 15px;
            border-radius:4px;
            cursor:pointer;
            box-shadow:0 2px 8px rgba(0,0,0,0.2);
            z-index:9999;
            font-family:-apple-system, system-ui, sans-serif;
            font-size:14px;
            transition:all 0.2s ease;
            border:none;`,
        modal: `
            position:fixed;
            top:50%;
            left:50%;
            transform:translate(-50%, -50%);
            background:#2f2f2f;
            color:#dadada;
            padding:25px;
            border-radius:4px;
            box-shadow:0 2px 20px rgba(0,0,0,0.3);
            z-index:10000;
            min-width:300px;
            max-width:90%;
            max-height:90vh;
            overflow-y:auto;
            font-family:-apple-system, system-ui, sans-serif;`,
        settings: `
            margin:0 0 20px 0;
            color:#da8e35;
            font-size:18px;
            font-weight:600;`,
        section: `
            background:#363636;
            padding:15px;
            border-radius:4px;
            margin-bottom:15px;`,
        sectionHeader: `
            color:#da8e35;
            margin:0 0 10px 0;
            font-size:16px;
            font-weight:500;`,
        input: `
            background:#2f2f2f;
            border:1px solid #444;
            color:#dadada;
            border-radius:3px;
            padding:5px;`,
        btn: {
            primary: `
                padding:8px 15px;
                border:none;
                background:#da8e35;
                color:white;
                border-radius:3px;
                cursor:pointer;
                transition:all 0.2s ease;`,
            secondary: `
                padding:8px 15px;
                border:1px solid #da8e35;
                background:transparent;
                color:#da8e35;
                border-radius:3px;
                cursor:pointer;
                transition:all 0.2s ease;`,
            advanced: `
                padding: 4px 8px;
                border: none;
                background: transparent;
                color: #666;
                font-size: 12px;
                cursor: pointer;
                transition: all 0.2s ease;
                opacity: 0.6;
                text-decoration: underline;
                &:hover {
                    opacity: 1;
                    color: #da8e35;
                }`
        }
    };

    function createSettingsUI() {
        const btn = document.createElement('div');
        btn.innerHTML = 'NexusNoWait++ ⚙️';
        btn.style.cssText = STYLES.button;

        btn.onmouseover = () => btn.style.transform = 'translateY(-2px)';
        btn.onmouseout = () => btn.style.transform = 'translateY(0)';
        btn.onclick = () => {
            if (activeModal) {
                activeModal.remove();
                activeModal = null;
                if (settingsChanged) {  // Only reload if settings were changed
                    location.reload();
                }
            } else {
                showSettingsModal();
            }
        };
        document.body.appendChild(btn);
    }

    //  settings UI
    /**
     * Creates settings UI HTML
     * @returns {string} Generated HTML
     */
    function generateSettingsHTML() {
        const normalBooleanSettings = Object.entries(SETTING_UI)
            .filter(([key]) => typeof config[key] === 'boolean' && !['debug'].includes(key))
            .map(([key, {name, description}]) => `
                <div style="margin-bottom:10px;">
                    <label title="${description}" style="display:flex;align-items:center;gap:8px;">
                        <input type="checkbox"
                               ${config[key] ? 'checked' : ''}
                               data-setting="${key}">
                        <span>${name}</span>
                    </label>
                </div>`).join('');

        const numberSettings = Object.entries(SETTING_UI)
            .filter(([key]) => typeof config[key] === 'number')
            .map(([key, {name, description}]) => `
                <div style="margin-bottom:10px;">
                    <label title="${description}" style="display:flex;align-items:center;justify-content:space-between;">
                        <span>${name}:</span>
                        <input type="number"
                               value="${config[key]}"
                               min="0"
                               step="100"
                               data-setting="${key}"
                               style="${STYLES.input};width:120px;">
                    </label>
                </div>`).join('');

        // debug section
        const advancedSection = `
            <div id="advancedSection" style="display:none;">
                <div style="${STYLES.section}">
                    <h4 style="${STYLES.sectionHeader}">Advanced Settings</h4>
                    <div style="margin-bottom:10px;">
                        <label title="${SETTING_UI.debug.description}" style="display:flex;align-items:center;gap:8px;">
                            <input type="checkbox"
                                   ${config.debug ? 'checked' : ''}
                                   data-setting="debug">
                            <span>${SETTING_UI.debug.name}</span>
                        </label>
                    </div>
                </div>
            </div>`;

        return `
            <h3 style="${STYLES.settings}">NexusNoWait++ Settings</h3>
            <div style="${STYLES.section}">
                <h4 style="${STYLES.sectionHeader}">Features</h4>
                ${normalBooleanSettings}
            </div>
            <div style="${STYLES.section}">
                <h4 style="${STYLES.sectionHeader}">Timing</h4>
                ${numberSettings}
            </div>
            ${advancedSection}
            <div style="margin-top:20px;display:flex;justify-content:center;gap:10px;">
                <button id="resetSettings" style="${STYLES.btn.secondary}">Reset</button>
                <button id="closeSettings" style="${STYLES.btn.primary}">Save & Close</button>
            </div>
            <div style="text-align: center; margin-top: 15px;">
                <button id="toggleAdvanced" style="${STYLES.btn.advanced}">⚙️ Advanced</button>
            </div>
            <div style="text-align: center; margin-top: 15px; color: #666; font-size: 12px;">
                Version ${GM_info.script.version}
                \n by Torkelicious
            </div>`;
    }

    let activeModal = null;
    let settingsChanged = false;  // Track settings changes

    /**
     * Shows settings and handles interactions
     * @returns {void}
     */
    function showSettingsModal() {
        if (activeModal) {
            activeModal.remove();
        }

        settingsChanged = false;  // Reset change tracker
        const modal = document.createElement('div');
        modal.style.cssText = STYLES.modal;

        modal.innerHTML = generateSettingsHTML();

        //  update function
        function updateSetting(element) {
            const setting = element.getAttribute('data-setting');
            const value = element.type === 'checkbox' ?
                element.checked :
                parseInt(element.value, 10);

            if (typeof value === 'number' && isNaN(value)) {
                element.value = config[setting];
                return;
            }

            if (config[setting] !== value) {
                settingsChanged = true;
                window.nexusConfig.setFeature(setting, value);
            }
        }

        modal.addEventListener('change', (e) => {
            if (e.target.hasAttribute('data-setting')) {
                updateSetting(e.target);
            }
        });

        modal.addEventListener('input', (e) => {
            if (e.target.type === 'number' && e.target.hasAttribute('data-setting')) {
                updateSetting(e.target);
            }
        });

        modal.querySelector('#closeSettings').onclick = () => {
            modal.remove();
            activeModal = null;
            // Only reload if settings were changed
            if (settingsChanged) {
                location.reload();
            }
        };

        modal.querySelector('#resetSettings').onclick = () => {
            settingsChanged = true;  // Reset counts as a change
            window.nexusConfig.reset();
            saveSettings(config);
            modal.remove();
            activeModal = null;
            location.reload();
        };

        // toggle handler for advanced section
        modal.querySelector('#toggleAdvanced').onclick = (e) => {
            const section = modal.querySelector('#advancedSection');
            const isHidden = section.style.display === 'none';
            section.style.display = isHidden ? 'block' : 'none';
            e.target.textContent = `Advanced ${isHidden ? '▲' : '▼'}`;
        };

        document.body.appendChild(modal);
        activeModal = modal;
    }

    // Override console when debug is enabled
    function setupDebugMode() {
        if (config.debug) {
            const originalConsole = {
                log: console.log,
                warn: console.warn,
                error: console.error
            };

            console.log = function() {
                originalConsole.log.apply(console, arguments);
                alert("[Debug Log]\n" + Array.from(arguments).join(' '));
            };

            console.warn = function() {
                originalConsole.warn.apply(console, arguments);
                alert("[Debug Warn]\n" + Array.from(arguments).join(' '));
            };

            console.error = function() {
                originalConsole.error.apply(console, arguments);
                alert("[Debug Error]\n" + Array.from(arguments).join(' '));
            };
        }
    }

    // === Configuration ===
    window.nexusConfig = {
        /**
         * Sets a feature setting
         * @param {string} name - Setting name
         * @param {any} value - Setting value
         */
        setFeature: (name, value) => {
            const oldValue = config[name];
            config[name] = value;
            saveSettings(config);

            // Only apply non-debug settings fast
            if (name !== 'debug') {
                applySettings();
            }

            // Mark settings as changed if value actually changed
            if (oldValue !== value) {
                settingsChanged = true;
            }
        },


         // Resets all settings to defaults

        reset: () => {
            GM_deleteValue('nexusNoWaitConfig');
            Object.assign(config, DEFAULT_CONFIG);
            saveSettings(config);
            applySettings();  // Apply changes
        },


        // Gets current configuration

        getConfig: () => config
    };

    function applySettings() {
        // Update AJAX timeout
        if (ajaxRequestRaw) {
            ajaxRequestRaw.timeout = config.requestTimeout;
        }
        setupDebugMode();
    }
// ------------------------------------------------------------------------------------------------ //

    // ===  Initialization ===
    /**
     * Checks if current URL is a mod page
     * @returns {boolean} True if URL matches mod pattern
     */
    function isModPage() {
        return /nexusmods\.com\/.*\/mods\//.test(window.location.href);
    }


    //Initializes UI components
    function initializeUI() {
        applySettings();
        createSettingsUI();
    }

    
    //Initializes main functions if on modpage
    function initMainFunctions() {
        if (!isModPage()) return;
        
        archivedFile();
        addClickListeners(document.querySelectorAll("a.btn"));
        autoStartFileLink();
        if (config.skipRequirements) {
            autoClickRequiredFileDownload();
        }
    }

    // Combined observer
    const mainObserver = new MutationObserver((mutations) => {
        if (!isModPage()) return;
        
        try {
            mutations.forEach(mutation => {
                if (!mutation.addedNodes) return;

                mutation.addedNodes.forEach(node => {
                    if (node.tagName === "A" && node.classList?.contains("btn")) {
                        addClickListener(node);
                    }

                    if (node.querySelectorAll) {
                        addClickListeners(node.querySelectorAll("a.btn"));
                    }
                });
            });
        } catch (error) {
            console.error("Error in mutation observer:", error);
        }
    });

    // Initialize everything
    initializeUI();
    initMainFunctions();

    // Start observing
    mainObserver.observe(document, {
        childList: true,
        subtree: true
    });

    // Cleanup on page unload
    window.addEventListener('unload', () => {
        mainObserver.disconnect();
    });
})();