Twitch Enhancements

Automatically claim channel points, enable theater mode, claim prime rewards, claim drops, and add redeem buttons for GOG and Legacy Games on Twitch and Amazon Gaming websites.

目前为 2025-03-20 提交的版本。查看 最新版本

// ==UserScript==
// @name         Twitch Enhancements
// @namespace    http://tampermonkey.net/
// @version      0.5.4
// @description  Automatically claim channel points, enable theater mode, claim prime rewards, claim drops, and add redeem buttons for GOG and Legacy Games on Twitch and Amazon Gaming websites.
// @author       JJJ
// @match        https://www.twitch.tv/*
// @match        https://gaming.amazon.com/*
// @match        https://www.twitch.tv/drops/inventory*
// @match        https://www.gog.com/en/redeem
// @match        https://promo.legacygames.com/*
// @icon         https://th.bing.com/th/id/R.d71be224f193da01e7e499165a8981c5?rik=uBYlAxJ4XyXmJg&riu=http%3a%2f%2fpngimg.com%2fuploads%2ftwitch%2ftwitch_PNG28.png&ehk=PMc5m5Fil%2bhyq1zilk3F3cuzxSluXFBE80XgxVIG0rM%3d&risl=&pid=ImgRaw&r=0
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Configuration settings
    const CONFIG = {
        enableAutoClaimPoints: GM_getValue('enableAutoClaimPoints', true),
        enableTheaterMode: GM_getValue('enableTheaterMode', true),
        enableClaimPrimeRewards: GM_getValue('enableClaimPrimeRewards', true),
        enableClaimDrops: GM_getValue('enableClaimDrops', true),
        enableGogRedeemButton: GM_getValue('enableGogRedeemButton', true),
        enableLegacyGamesRedeemButton: GM_getValue('enableLegacyGamesRedeemButton', true),
        enableHideGlobalMenu: GM_getValue('enableHideGlobalMenu', true),
        enableAutoRefreshDrops: GM_getValue('enableAutoRefreshDrops', true),
        enableClaimAllButton: GM_getValue('enableClaimAllButton', true),
        enableRemoveAllButton: GM_getValue('enableRemoveAllButton', true),
        settingsKey: GM_getValue('settingsKey', 'F2') // Default to F2 if not set
    };

    // Add logger configuration
    const Logger = {
        styles: {
            info: 'color: #2196F3; font-weight: bold',
            warning: 'color: #FFC107; font-weight: bold',
            success: 'color: #4CAF50; font-weight: bold',
            error: 'color: #F44336; font-weight: bold'
        },
        prefix: '[TwitchEnhancements]',
        getTimestamp() {
            return new Date().toISOString().split('T')[1].slice(0, -1);
        },
        info(msg) {
            console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.info);
        },
        warning(msg) {
            console.warn(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.warning);
        },
        success(msg) {
            console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.success);
        },
        error(msg) {
            console.error(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.error);
        }
    };

    // Twitch Constants
    const PLAYER_SELECTOR = '.video-player';
    const THEATER_MODE_BUTTON_SELECTOR = 'button[aria-label="Modo cine (alt+t)"], button[aria-label="Theatre Mode (alt+t)"]';
    const CLOSE_MENU_BUTTON_SELECTOR = 'button[aria-label="Close Menu"]';
    const CLOSE_MODAL_BUTTON_SELECTOR = 'button[aria-label="Close modal"]';
    const THEATER_MODE_CLASS = 'theatre-mode';
    const CLAIMABLE_BONUS_SELECTOR = '.claimable-bonus__icon';
    const CLAIM_DROPS_SELECTOR = 'button.ScCoreButton-sc-ocjdkq-0.eWlfQB';
    const PRIME_REWARD_SELECTOR = 'button.tw-interactive.tw-button.tw-button--full-width[data-a-target="buy-box_call-to-action"] span.tw-button__text div.tw-inline-block p.tw-font-size-5.tw-md-font-size-4[title="Get game"]';
    const PRIME_REWARD_SELECTOR_2 = 'p.tw-font-size-5.tw-md-font-size-4[data-a-target="buy-box_call-to-action-text"][title="Get game"]';

    // Redeem on GOG Constants
    const GOG_REDEEM_CODE_INPUT_SELECTOR = '#codeInput';
    const GOG_CONTINUE_BUTTON_SELECTOR = 'button[type="submit"][aria-label="Proceed to the next step"]';
    const GOG_FINAL_REDEEM_BUTTON_SELECTOR = 'button[type="submit"][aria-label="Redeem the code"]';

    // Redeem on Legacy Games Constants
    const LEGACY_GAMES_REDEEM_URL = 'https://promo.legacygames.com/royal-romances-cursed-hearts-ce-prime-deal/';
    const LEGACY_GAMES_CODE_INPUT_SELECTOR = '#primedeal_game_code';
    const LEGACY_GAMES_EMAIL_INPUT_SELECTOR = '#primedeal_email';
    const LEGACY_GAMES_EMAIL_VALIDATE_INPUT_SELECTOR = '#primedeal_email_validate';
    const LEGACY_GAMES_SUBMIT_BUTTON_SELECTOR = '#submitbutton';
    const LEGACY_GAMES_NEWSLETTER_CHECKBOX_SELECTOR = '#primedeal_newsletter';

    let claiming = false;

    // Check if MutationObserver is supported
    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;

    // Settings Dialog Functions
    function createSettingsDialog() {
        const dialogHTML = `
            <div id="twitchEnhancementsDialog" class="te-dialog">
                <h3>Twitch Enhancements Settings</h3>
                ${createToggle('enableAutoClaimPoints', 'Auto Claim Channel Points', 'Automatically claim channel points')}
                ${createToggle('enableTheaterMode', 'Auto Theater Mode', 'Automatically enable theater mode')}
                ${createToggle('enableClaimPrimeRewards', 'Auto Claim Prime Rewards', 'Automatically claim prime rewards')}
                ${createToggle('enableClaimDrops', 'Auto Claim Drops', 'Automatically claim Twitch drops')}
                ${createToggle('enableGogRedeemButton', 'GOG Redeem Button', 'Add GOG redeem button on Amazon Gaming')}
                ${createToggle('enableLegacyGamesRedeemButton', 'Legacy Games Button', 'Add Legacy Games redeem button on Amazon Gaming')}
                ${createToggle('enableHideGlobalMenu', 'Hide Global Menu', 'Hide the global menu on Twitch')}
                ${createToggle('enableAutoRefreshDrops', 'Auto Refresh Drops', 'Automatically refresh drops inventory page every 15 minutes')}
                ${createToggle('enableClaimAllButton', 'Enable Claim All Button', 'Add Claim All button on Amazon Gaming')}
                ${createToggle('enableRemoveAllButton', 'Enable Remove All Button', 'Add Remove All button on Amazon Gaming')}
                <div class="te-key-setting">
                    <label for="settingsKey" class="te-key-label">Settings Toggle Key:</label>
                    <div class="te-key-input-container">
                        <input type="text" id="settingsKey" class="te-key-input" value="${CONFIG.settingsKey}" readonly>
                        <button id="changeKeyButton" class="te-key-button">Change Key</button>
                    </div>
                    <div id="keyInstructions" class="te-key-instructions" style="display:none;">Press any key...</div>
                </div>
                <div class="te-button-container">
                    <button id="saveSettingsButton" class="te-button te-button-save">Save</button>
                    <button id="cancelSettingsButton" class="te-button te-button-cancel">Cancel</button>
                </div>
            </div>
        `;

        const styleSheet = `
            <style>
                .te-dialog {
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: rgba(18, 16, 24, 0.9);
                    border: 1px solid #772ce8;
                    border-radius: 8px;
                    padding: 20px;
                    box-shadow: 0 0 20px rgba(0, 0, 0, 0.7);
                    z-index: 9999999; /* Increased z-index to ensure it appears above all elements */
                    color: white;
                    width: 350px;
                    font-family: 'Roobert', 'Inter', Helvetica, Arial, sans-serif;
                }
                .te-dialog h3 {
                    margin-top: 0;
                    font-size: 1.4em;
                    text-align: center;
                    margin-bottom: 20px;
                    color: #bf94ff;
                }
                .te-toggle-container {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 15px;
                }
                .te-toggle-label {
                    flex-grow: 1;
                    font-size: 0.95em;
                }
                .te-toggle {
                    position: relative;
                    display: inline-block;
                    width: 50px;
                    height: 24px;
                }
                .te-toggle input {
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    opacity: 0;
                    cursor: pointer;
                    margin: 0;
                }
                .te-toggle-slider {
                    position: absolute;
                    cursor: pointer;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background-color: #333;
                    transition: .4s;
                    border-radius: 24px;
                }
                .te-toggle-slider:before {
                    position: absolute;
                    content: "";
                    height: 16px;
                    width: 16px;
                    left: 4px;
                    bottom: 4px;
                    background-color: white;
                    transition: .4s;
                    border-radius: 50%;
                }
                .te-toggle input:checked + .te-toggle-slider {
                    background-color: #9147ff;
                }
                .te-toggle input:checked + .te-toggle-slider:before {
                    transform: translateX(26px);
                }
                .te-button-container {
                    display: flex;
                    justify-content: space-between;
                    margin-top: 20px;
                }
                .te-button {
                    padding: 8px 16px;
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                    font-size: 0.95em;
                    transition: background-color 0.3s;
                }
                .te-button-save {
                    background-color: #9147ff;
                    color: white;
                }
                .te-button-save:hover {
                    background-color: #772ce8;
                }
                .te-button-cancel {
                    background-color: #464649;
                    color: white;
                }
                .te-button-cancel:hover {
                    background-color: #2d2d30;
                }
                .te-key-setting {
                    margin-top: 20px;
                    padding-top: 15px;
                    border-top: 1px solid #464649;
                }
                .te-key-label {
                    display: block;
                    margin-bottom: 10px;
                    font-size: 0.95em;
                }
                .te-key-input-container {
                    display: flex;
                    gap: 10px;
                }
                .te-key-input {
                    flex: 1;
                    background-color: #18181b;
                    color: white;
                    border: 1px solid #464649;
                    border-radius: 4px;
                    padding: 8px;
                    text-align: center;
                    font-size: 14px;
                }
                .te-key-button {
                    background-color: #464649;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 8px 12px;
                    cursor: pointer;
                    font-size: 0.85em;
                }
                .te-key-button:hover {
                    background-color: #5c5c5f;
                }
                .te-key-instructions {
                    margin-top: 10px;
                    font-size: 0.85em;
                    color: #bf94ff;
                    text-align: center;
                }
            </style>
        `;

        const dialogWrapper = document.createElement('div');
        dialogWrapper.innerHTML = styleSheet + dialogHTML;
        document.body.appendChild(dialogWrapper);

        // Add event listeners to toggles with improved feedback - MODIFIED
        // Store toggle changes in memory instead of immediately updating CONFIG
        const pendingChanges = {}; // Object to track pending changes

        document.querySelectorAll('.te-toggle input').forEach(toggle => {
            toggle.addEventListener('change', (event) => {
                const { id, checked } = event.target;
                Logger.info(`Toggle changed: ${id} = ${checked}`);
                // Instead of updating CONFIG directly, store the pending change
                pendingChanges[id] = checked;
            });
        });

        // Add event listeners to buttons
        document.getElementById('saveSettingsButton').addEventListener('click', () => saveAndCloseDialog(pendingChanges));
        document.getElementById('cancelSettingsButton').addEventListener('click', closeDialog);

        // Add event listener for change key button
        const changeKeyButton = document.getElementById('changeKeyButton');
        changeKeyButton.addEventListener('click', function () {
            const keyInput = document.getElementById('settingsKey');
            const keyInstructions = document.getElementById('keyInstructions');

            // Show instructions and focus on input
            keyInstructions.style.display = 'block';
            keyInstructions.textContent = 'Press key combination (e.g. Ctrl+Shift+K)...';
            keyInput.value = 'Press keys...';

            // Change button text to indicate canceling is possible
            changeKeyButton.textContent = 'Cancel';

            // Flag to track if we're in key capture mode
            let capturingKey = true;

            // Variables to store key combination
            let modifiers = {
                ctrl: false,
                alt: false,
                shift: false,
                meta: false
            };
            let mainKey = '';

            // Function to format current key combination
            const formatKeyCombination = () => {
                const parts = [];
                if (modifiers.ctrl) parts.push('Ctrl');
                if (modifiers.alt) parts.push('Alt');
                if (modifiers.shift) parts.push('Shift');
                if (modifiers.meta) parts.push('Meta');
                if (mainKey && !['Control', 'Alt', 'Shift', 'Meta'].includes(mainKey)) {
                    parts.push(mainKey);
                }
                return parts.join('+');
            };

            // Function to update the input with current combination
            const updateKeyDisplay = () => {
                const combination = formatKeyCombination();
                if (combination) {
                    keyInput.value = combination;
                } else {
                    keyInput.value = 'Press keys...';
                }
            };

            // Function to handle key down
            const handleKeyDown = function (e) {
                if (!capturingKey) return;

                e.preventDefault();
                e.stopPropagation();

                // Track modifier keys
                if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') {
                    switch (e.key) {
                        case 'Control': modifiers.ctrl = true; break;
                        case 'Alt': modifiers.alt = true; break;
                        case 'Shift': modifiers.shift = true; break;
                        case 'Meta': modifiers.meta = true; break;
                    }
                } else {
                    // Track main key
                    mainKey = e.key;
                }

                // Update the display
                updateKeyDisplay();
            };

            // Function to handle key up
            const handleKeyUp = function (e) {
                if (!capturingKey) return;

                e.preventDefault();
                e.stopPropagation();

                // Handle modifier keys being released
                if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') {
                    switch (e.key) {
                        case 'Control': modifiers.ctrl = false; break;
                        case 'Alt': modifiers.alt = false; break;
                        case 'Shift': modifiers.shift = false; break;
                        case 'Meta': modifiers.meta = false; break;
                    }

                    // Update the display
                    updateKeyDisplay();
                } else {
                    // If a non-modifier key was released, complete the capture
                    const keyCombination = formatKeyCombination();

                    // Only save if we have a valid combination (at least one key)
                    if (keyCombination && keyCombination !== 'Press keys...') {
                        keyInput.value = keyCombination;

                        // Exit key capture mode
                        document.removeEventListener('keydown', handleKeyDown, true);
                        document.removeEventListener('keyup', handleKeyUp, true);
                        keyInstructions.style.display = 'none';
                        changeKeyButton.textContent = 'Change Key';
                        capturingKey = false;

                        // Log the captured combination
                        Logger.info(`Key combination captured: ${keyCombination}`);
                    }
                }
            };

            // Function to cancel key capture
            const cancelCapture = function () {
                if (!capturingKey) return;

                document.removeEventListener('keydown', handleKeyDown, true);
                document.removeEventListener('keyup', handleKeyUp, true);
                keyInput.value = CONFIG.settingsKey;
                keyInstructions.style.display = 'none';
                changeKeyButton.textContent = 'Change Key';
                capturingKey = false;
            };

            // Allow canceling key capture by clicking the button again
            changeKeyButton.addEventListener('click', cancelCapture, { once: true });

            // Capture key events
            document.addEventListener('keydown', handleKeyDown, true);
            document.addEventListener('keyup', handleKeyUp, true);
        });
    }

    function createToggle(id, label, title) {
        return `
            <div class="te-toggle-container" title="${title}">
                <label class="te-toggle">
                    <input type="checkbox" id="${id}" ${CONFIG[id] ? 'checked' : ''}>
                    <span class="te-toggle-slider"></span>
                </label>
                <label for="${id}" class="te-toggle-label">${label}</label>
            </div>
        `;
    }

    // Modified saveAndCloseDialog function to apply changes dynamically
    function saveAndCloseDialog(pendingChanges = {}) {
        // Create a deep copy of the CONFIG object before any changes are made
        const oldConfig = JSON.parse(JSON.stringify(CONFIG));
        let changesMade = false;

        // Improved debugging output
        Logger.info("Checking for settings changes...");

        // Save toggle settings
        Object.keys(CONFIG).forEach(key => {
            if (key === 'settingsKey') return; // Handle separately

            // Check if this setting has a pending change
            if (pendingChanges.hasOwnProperty(key)) {
                const oldValue = oldConfig[key];
                const newValue = pendingChanges[key];

                // Log the comparison for debugging
                Logger.info(`Comparing ${key}: old=${oldValue} (${typeof oldValue}), new=${newValue} (${typeof newValue})`);

                // Compare values - both should be booleans for toggle settings
                if (oldValue !== newValue) {
                    changesMade = true;
                    Logger.info(`Changed ${key} from ${oldValue} to ${newValue}`);
                    CONFIG[key] = newValue;
                    GM_setValue(key, newValue);
                }
            } else {
                // If no pending change, get value from form element
                const element = document.getElementById(key);
                if (element) {
                    const oldValue = oldConfig[key];
                    const newValue = element.checked;

                    // Log the comparison for debugging
                    Logger.info(`Comparing ${key}: old=${oldValue} (${typeof oldValue}), new=${newValue} (${typeof newValue})`);

                    // Compare values
                    if (oldValue !== newValue) {
                        changesMade = true;
                        Logger.info(`Changed ${key} from ${oldValue} to ${newValue}`);
                        CONFIG[key] = newValue;
                        GM_setValue(key, newValue);
                    }
                }
            }
        });

        // Save settings key
        const keyInput = document.getElementById('settingsKey');
        if (keyInput && keyInput.value !== oldConfig.settingsKey) {
            changesMade = true;
            Logger.info(`Changed settings key from ${oldConfig.settingsKey} to ${keyInput.value}`);
            CONFIG.settingsKey = keyInput.value;
            GM_setValue('settingsKey', keyInput.value);
        }

        closeDialog();

        if (changesMade) {
            Logger.success('Settings saved and applied immediately');
            applySettingsChanges(oldConfig);
        } else {
            // Show more helpful message when no changes are detected
            Logger.info('No changes detected. Settings remain the same.');
        }
    }

    // Function to dynamically apply settings changes
    function applySettingsChanges(oldConfig) {
        // Restart observers or update UI elements based on config changes

        // Handle auto refresh drops changes
        if (oldConfig.enableAutoRefreshDrops !== CONFIG.enableAutoRefreshDrops) {
            setupAutoRefreshDrops();
        }

        // Handle claim points observer changes
        if (oldConfig.enableAutoClaimPoints !== CONFIG.enableAutoClaimPoints) {
            restartClaimPointsObserver();
        }

        // Handle claim drops observer changes
        if (oldConfig.enableClaimDrops !== CONFIG.enableClaimDrops) {
            restartClaimDropsObserver();
        }

        // Handle Amazon gaming buttons changes
        if (oldConfig.enableGogRedeemButton !== CONFIG.enableGogRedeemButton ||
            oldConfig.enableLegacyGamesRedeemButton !== CONFIG.enableLegacyGamesRedeemButton) {
            updateRedeeemButtons();
        }

        // Handle PrimeOfferPopover changes for Claim All/Remove All buttons
        if (oldConfig.enableClaimAllButton !== CONFIG.enableClaimAllButton ||
            oldConfig.enableRemoveAllButton !== CONFIG.enableRemoveAllButton) {
            if (document.getElementById("PrimeOfferPopover-header")) {
                updatePrimeOfferButtons();
            }
        }

        // Handle Theater Mode changes
        if (!oldConfig.enableTheaterMode && CONFIG.enableTheaterMode) {
            enableTheaterMode();
        }

        // Handle Hide Global Menu changes
        if (CONFIG.enableHideGlobalMenu) {
            hideGlobalMenu();
        } else if (!CONFIG.enableHideGlobalMenu && oldConfig.enableHideGlobalMenu) {
            showGlobalMenu();
        }
    }

    // Function to show global menu (when setting is turned off)
    function showGlobalMenu() {
        const GLOBAL_MENU_SELECTOR = 'div.ScBalloonWrapper-sc-14jr088-0.eEhNFm';
        const globalMenu = document.querySelector(GLOBAL_MENU_SELECTOR);
        if (globalMenu) {
            globalMenu.style.display = '';
            Logger.info('Global menu restored');
        }
    }

    // Variables to track observers
    let claimPointsObserver = null;
    let claimDropsObserver = null;
    let autoRefreshInterval = null;

    // Function to restart claim points observer
    function restartClaimPointsObserver() {
        if (claimPointsObserver) {
            claimPointsObserver.disconnect();
            claimPointsObserver = null;
            Logger.info('Auto claim points observer disconnected');
        }

        if (CONFIG.enableAutoClaimPoints) {
            setupAutoClaimBonus();
        }
    }

    // Function to restart claim drops observer
    function restartClaimDropsObserver() {
        if (claimDropsObserver) {
            claimDropsObserver.disconnect();
            claimDropsObserver = null;
            Logger.info('Claim drops observer disconnected');
        }

        if (CONFIG.enableClaimDrops) {
            setupClaimDrops();
        }
    }

    // Function to setup auto refresh drops timer
    function setupAutoRefreshDrops() {
        if (autoRefreshInterval) {
            clearInterval(autoRefreshInterval);
            autoRefreshInterval = null;
            Logger.info('Auto refresh drops timer cleared');
        }

        if (CONFIG.enableAutoRefreshDrops) {
            autoRefreshInterval = setInterval(function () {
                if (window.location.href.startsWith('https://www.twitch.tv/drops/inventory')) {
                    Logger.info('Auto-refreshing drops inventory page');
                    window.location.reload();
                }
            }, 15 * 60000);
            Logger.info('Auto refresh drops timer started');
        }
    }

    // Function to update redeem buttons
    function updateRedeeemButtons() {
        if (window.location.hostname === 'gaming.amazon.com') {
            if (CONFIG.enableGogRedeemButton) {
                addGogRedeemButton();
            } else {
                // Remove GOG buttons
                const gogButtons = document.querySelectorAll('.gog-redeem-button');
                gogButtons.forEach(button => button.remove());
                Logger.info('GOG redeem buttons removed');
            }

            if (CONFIG.enableLegacyGamesRedeemButton) {
                addLegacyGamesRedeemButton();
            } else {
                // Remove Legacy Games buttons
                const legacyButtons = document.querySelectorAll('.legacy-games-redeem-button');
                legacyButtons.forEach(button => button.remove());
                Logger.info('Legacy Games redeem buttons removed');
            }
        }
    }

    // Function to update the Prime Offer Popover buttons
    function updatePrimeOfferButtons() {
        const primeOfferHeader = document.getElementById("PrimeOfferPopover-header");
        if (!primeOfferHeader) return;

        let o = new MutationObserver((m) => {
            if (!CONFIG.enableClaimAllButton && !CONFIG.enableRemoveAllButton) {
                // Remove all custom buttons
                const customButtonsContainer = document.querySelector('#PrimeOfferPopover-header > div');
                if (customButtonsContainer) {
                    customButtonsContainer.remove();
                }
                return;
            }

            // Trigger a refresh of the buttons
            const headerElement = document.getElementById("PrimeOfferPopover-header");
            if (headerElement) {
                // Force refresh by triggering our main observer
                const dummyDiv = document.createElement('div');
                document.body.appendChild(dummyDiv);
                document.body.removeChild(dummyDiv);
            }
        });

        // Trigger the observer
        o.observe(document.body, { childList: true });
        setTimeout(() => o.disconnect(), 500); // Disconnect after a short time
    }

    // Function to setup auto claim bonus
    function setupAutoClaimBonus() {
        if (!CONFIG.enableAutoClaimPoints || !MutationObserver) return;

        Logger.info('Auto claimer is enabled.');

        claimPointsObserver = new MutationObserver(mutationsList => {
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList' && CONFIG.enableAutoClaimPoints) {
                    let bonus = document.querySelector(CLAIMABLE_BONUS_SELECTOR);
                    if (bonus && !claiming) {
                        bonus.click();
                        let date = new Date();
                        claiming = true;
                        setTimeout(() => {
                            Logger.success('Claimed at ' + date.toLocaleString());
                            claiming = false;
                        }, Math.random() * 1000 + 2000);
                    }
                }
            }
        });

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

    // Function to setup claim drops
    function setupClaimDrops() {
        if (!CONFIG.enableClaimDrops || !MutationObserver) return;

        var onMutate = function (mutationsList) {
            mutationsList.forEach(mutation => {
                if (CONFIG.enableClaimDrops && document.querySelector(CLAIM_DROPS_SELECTOR)) {
                    document.querySelector(CLAIM_DROPS_SELECTOR).click();
                }
            });
        };

        claimDropsObserver = new MutationObserver(onMutate);
        claimDropsObserver.observe(document.body, { childList: true, subtree: true });
        Logger.info('Claim drops observer started');
    }

    function closeDialog() {
        const dialog = document.getElementById('twitchEnhancementsDialog');
        if (dialog) {
            dialog.remove();
        }
    }

    function toggleSettingsDialog() {
        const dialog = document.getElementById('twitchEnhancementsDialog');
        if (dialog) {
            dialog.remove();
        } else {
            createSettingsDialog();
        }
    }

    // Register menu command
    GM_registerMenuCommand('Twitch Enhancements Settings', toggleSettingsDialog);

    // Function to click a button
    function clickButton(buttonSelector) {
        if (!MutationObserver) return;

        const observer = new MutationObserver((mutationsList, observer) => {
            for (let mutation of mutationsList) {
                if (mutation.addedNodes.length) {
                    const button = document.querySelector(buttonSelector);
                    if (button) {
                        button.click();
                        observer.disconnect();
                        return;
                    }
                }
            }
        });

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

    // Function to enable theater mode
    function enableTheaterMode() {
        if (!CONFIG.enableTheaterMode) return;

        const player = document.querySelector(PLAYER_SELECTOR);
        if (player) {
            if (!player.classList.contains(THEATER_MODE_CLASS)) {
                clickButton(THEATER_MODE_BUTTON_SELECTOR);
            }
        } else {
            Logger.error('Player not found');
        }
    }

    // Function to hide the global menu
    function hideGlobalMenu() {
        if (!CONFIG.enableHideGlobalMenu) return;

        const GLOBAL_MENU_SELECTOR = 'div.ScBalloonWrapper-sc-14jr088-0.eEhNFm';
        const globalMenu = document.querySelector(GLOBAL_MENU_SELECTOR);
        if (globalMenu) {
            globalMenu.style.display = 'none';
        } else {
            Logger.error('Global menu not found');
        }
    }

    // Function to automatically claim channel points
    function autoClaimBonus() {
        if (!CONFIG.enableAutoClaimPoints || !MutationObserver) return;

        Logger.info('Auto claimer is enabled.');

        let observer = new MutationObserver(mutationsList => {
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    let bonus = document.querySelector(CLAIMABLE_BONUS_SELECTOR);
                    if (bonus && !claiming) {
                        bonus.click();
                        let date = new Date();
                        claiming = true;
                        setTimeout(() => {
                            Logger.success('Claimed at ' + date.toLocaleString());
                            claiming = false;
                        }, Math.random() * 1000 + 2000);
                    }
                }
            }
        });

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

    // Function to claim prime rewards with retry
    function claimPrimeReward() {
        if (!CONFIG.enableClaimPrimeRewards) return;

        const maxAttempts = 5;
        let attempts = 0;

        const tryClaim = () => {
            if (attempts >= maxAttempts) {
                Logger.warning('Max attempts reached for claiming prime reward');
                return;
            }
            attempts++;

            const element = document.querySelector(PRIME_REWARD_SELECTOR) || document.querySelector(PRIME_REWARD_SELECTOR_2);
            if (element) {
                element.click();
                Logger.success('Prime reward claimed');
            } else {
                Logger.info(`Attempt ${attempts}/${maxAttempts}: Waiting for prime reward button...`);
                setTimeout(tryClaim, 1000);
            }
        };

        setTimeout(tryClaim, 2000);
    }

    // Function to claim drops
    function claimDrops() {
        if (!CONFIG.enableClaimDrops || !MutationObserver) return;

        var onMutate = function (mutationsList) {
            mutationsList.forEach(mutation => {
                if (document.querySelector(CLAIM_DROPS_SELECTOR)) document.querySelector(CLAIM_DROPS_SELECTOR).click();
            })
        }
        var observer = new MutationObserver(onMutate);
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Function to add the "Redeem on GOG" button
    function addGogRedeemButton() {
        if (!CONFIG.enableGogRedeemButton) return;

        const claimCodeButton = document.querySelector('p[title="Claim Code"]');
        if (claimCodeButton && !document.querySelector('.gog-redeem-button')) {
            const claimCodeWrapper = claimCodeButton.closest('.claim-button-wrapper');
            if (claimCodeWrapper) {
                const gogRedeemButtonDiv = document.createElement('div');
                gogRedeemButtonDiv.className = 'claim-button tw-align-self-center gog-redeem-button';

                const gogRedeemButton = document.createElement('a');
                gogRedeemButton.href = 'https://www.gog.com/en/redeem';
                gogRedeemButton.rel = 'noopener noreferrer';
                gogRedeemButton.className = 'tw-interactive tw-button tw-button--full-width';
                gogRedeemButton.dataset.aTarget = 'redeem-on-gog';
                gogRedeemButton.innerHTML = '<span class="tw-button__text" data-a-target="tw-button-text"><div class="tw-inline-flex"><p class="" title="Redeem on GOG">Redeem on GOG</p>&nbsp;&nbsp;<figure aria-label="ExternalLinkWithBox" class="tw-svg"><svg class="tw-svg__asset tw-svg__asset--externallinkwithbox tw-svg__asset--inherit" width="12px" height="12px" version="1.1" viewBox="0 0 11 11" x="0px" y="0px"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.3125 6.875V9.625C10.3125 10.3844 9.69689 11 8.9375 11H1.375C0.615608 11 0 10.3844 0 9.625V2.0625C0 1.30311 0.615608 0.6875 1.375 0.6875H4.125V2.0625H1.375V9.625H8.9375V6.875H10.3125ZM9.62301 2.34727L5.29664 6.67364L4.32437 5.70136L8.65073 1.375H6.18551V0H10.998V4.8125H9.62301V2.34727Z"></path></svg></figure></div></span>';

                gogRedeemButtonDiv.appendChild(gogRedeemButton);
                claimCodeWrapper.appendChild(gogRedeemButtonDiv);

                gogRedeemButton.addEventListener('click', function (e) {
                    e.preventDefault();
                    const codeInput = document.querySelector('input[aria-label]');
                    if (codeInput) {
                        const code = codeInput.value;
                        if (code) {
                            navigator.clipboard.writeText(code).then(function () {
                                window.location.href = 'https://www.gog.com/en/redeem';
                            });
                        }
                    }
                });

                const style = document.createElement('style');
                style.innerHTML = `
                    .claim-button-wrapper {
                        display: flex;
                        flex-direction: column;
                        margin-top: 15px;
                    }
                    .claim-button,
                    .gog-redeem-button {
                        margin: 5px 0;
                    }
                    .tw-mg-l-1 {
                        margin-top: 10px;
                    }
                    .claimable-item {
                        flex-direction: column !important;
                        gap: 15px;
                    }
                    .tw-flex-grow-1 {
                        width: 100%;
                    }
                `;
                document.head.appendChild(style);
            }
        }
    }

    // Function to redeem code on GOG
    function redeemCodeOnGOG() {
        navigator.clipboard.readText().then(function (code) {
            const codeInput = document.querySelector(GOG_REDEEM_CODE_INPUT_SELECTOR);
            if (codeInput) {
                codeInput.value = code;

                // Simulate input event to ensure any listeners are triggered
                const inputEvent = new Event('input', { bubbles: true });
                codeInput.dispatchEvent(inputEvent);

                // Click the continue button after a short delay
                setTimeout(() => {
                    const continueButton = document.querySelector(GOG_CONTINUE_BUTTON_SELECTOR);
                    if (continueButton) {
                        continueButton.click();

                        // Wait for the "Redeem" button to appear and click it
                        const checkRedeemButton = setInterval(() => {
                            const redeemButton = document.querySelector(GOG_FINAL_REDEEM_BUTTON_SELECTOR);
                            if (redeemButton) {
                                clearInterval(checkRedeemButton);
                                redeemButton.click();
                            }
                        }, 500); // Check every 500ms for the Redeem button
                    }
                }, 500); // Adjust the delay as needed
            }
        }).catch(function (err) {
            Logger.error('Failed to read clipboard contents: ' + err);
        });
    }

    // Function to add the "Redeem on Legacy Games" button
    function addLegacyGamesRedeemButton() {
        if (!CONFIG.enableLegacyGamesRedeemButton) return;

        const copyCodeButton = document.querySelector('button[aria-label="Copy code to your clipboard"]');
        if (copyCodeButton && !document.querySelector('.legacy-games-redeem-button')) {
            const copyCodeWrapper = copyCodeButton.closest('.copy-button-wrapper');
            if (copyCodeWrapper) {
                const legacyGamesRedeemButtonDiv = document.createElement('div');
                legacyGamesRedeemButtonDiv.className = 'copy-button tw-align-self-center legacy-games-redeem-button';

                const legacyGamesRedeemButton = document.createElement('button');
                legacyGamesRedeemButton.ariaLabel = 'Redeem on Legacy Games';
                legacyGamesRedeemButton.className = 'tw-interactive tw-button tw-button--full-width';
                legacyGamesRedeemButton.dataset.aTarget = 'redeem-on-legacy-games';
                legacyGamesRedeemButton.innerHTML = '<span class="tw-button__text" data-a-target="tw-button-text">Redeem on Legacy Games</span>';

                legacyGamesRedeemButtonDiv.appendChild(legacyGamesRedeemButton);
                copyCodeWrapper.appendChild(legacyGamesRedeemButtonDiv);

                legacyGamesRedeemButton.addEventListener('click', function (e) {
                    e.preventDefault();
                    const codeInput = document.querySelector('input[aria-label]');
                    if (codeInput) {
                        const code = codeInput.value;
                        if (code) {
                            navigator.clipboard.writeText(code).then(function () {
                                const email = GM_getValue('legacyGamesEmail', null);
                                if (!email) {
                                    const userEmail = prompt('Please enter your email address:');
                                    if (userEmail) {
                                        GM_setValue('legacyGamesEmail', userEmail);
                                        window.location.href = LEGACY_GAMES_REDEEM_URL;
                                    }
                                } else {
                                    window.location.href = LEGACY_GAMES_REDEEM_URL;
                                }
                            });
                        }
                    }
                });

                const style = document.createElement('style');
                style.innerHTML = `
                    .copy-button-wrapper {
                        display: flex;
                        flex-direction: column;
                        margin-top: 15px;
                    }
                    .copy-button,
                    .legacy-games-redeem-button {
                        margin: 5px 0;
                    }
                    .tw-mg-l-1 {
                        margin-top: 10px;
                    }
                    .claimable-item {
                        flex-direction: column !important;
                        gap: 15px;
                    }
                    .tw-flex-grow-1 {
                        width: 100%;
                    }
                `;
                document.head.appendChild(style);
            }
        }
    }

    // Function to redeem code on Legacy Games
    function redeemCodeOnLegacyGames() {
        const maxAttempts = 5;
        let attempts = 0;

        const tryRedeem = () => {
            if (attempts >= maxAttempts) return;
            attempts++;

            navigator.clipboard.readText().then(function (code) {
                const codeInput = document.querySelector(LEGACY_GAMES_CODE_INPUT_SELECTOR);
                const emailInput = document.querySelector(LEGACY_GAMES_EMAIL_INPUT_SELECTOR);
                const emailValidateInput = document.querySelector(LEGACY_GAMES_EMAIL_VALIDATE_INPUT_SELECTOR);
                const submitButton = document.querySelector(LEGACY_GAMES_SUBMIT_BUTTON_SELECTOR);
                const newsletterCheckbox = document.querySelector(LEGACY_GAMES_NEWSLETTER_CHECKBOX_SELECTOR);
                const email = GM_getValue('legacyGamesEmail', null);

                if (!codeInput || !emailInput || !emailValidateInput || !submitButton) {
                    Logger.info('Waiting for elements to load...');
                    setTimeout(tryRedeem, 1000);
                    return;
                }

                if (email && code) {
                    // Fill in the form
                    codeInput.value = code;
                    emailInput.value = email;
                    emailValidateInput.value = email;

                    // Ensure newsletter checkbox is unchecked
                    if (newsletterCheckbox) {
                        newsletterCheckbox.checked = false;
                    }

                    // Trigger input events
                    [codeInput, emailInput, emailValidateInput].forEach(input => {
                        input.dispatchEvent(new Event('input', { bubbles: true }));
                        input.dispatchEvent(new Event('change', { bubbles: true }));
                    });

                    // Submit the form
                    setTimeout(() => {
                        submitButton.click();
                        Logger.success('Form submitted with code: ' + code + ' and email: ' + email);
                    }, 500);
                }
            }).catch(function (err) {
                Logger.error('Failed to read clipboard contents: ' + err);
            });
        };

        // Start the redemption process
        setTimeout(tryRedeem, 2000);
    }

    // Function to open all "Claim Game" buttons in new tabs
    function openClaimGameTabs() {
        const claimGameButtons = document.querySelectorAll('div[data-a-target="tw-core-button-label-text"].Layout-sc-1xcs6mc-0.bFxzAY');
        claimGameButtons.forEach(button => {
            const parentButton = button.closest('a');
            if (parentButton) {
                window.open(parentButton.href, '_blank');
            }
        });
    }

    if (window.location.hostname === 'gaming.amazon.com') {
        const observer = new MutationObserver((mutations, obs) => {
            const claimCodeButton = document.querySelector('p[title="Claim Code"]');
            if (claimCodeButton && CONFIG.enableGogRedeemButton) {
                addGogRedeemButton();
            }
            const copyCodeButton = document.querySelector('button[aria-label="Copy code to your clipboard"]');
            if (copyCodeButton && CONFIG.enableLegacyGamesRedeemButton) {
                addLegacyGamesRedeemButton();
            }
        });

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

        if (CONFIG.enableGogRedeemButton) addGogRedeemButton();
        if (CONFIG.enableLegacyGamesRedeemButton) addLegacyGamesRedeemButton();
    }

    if (window.location.hostname === 'www.gog.com' && window.location.pathname === '/en/redeem') {
        window.addEventListener('load', redeemCodeOnGOG);
    }

    if (window.location.hostname === 'promo.legacygames.com') {
        window.addEventListener('load', redeemCodeOnLegacyGames);
    }

    setTimeout(enableTheaterMode, 1000);
    setTimeout(setupAutoClaimBonus, 1000);
    setTimeout(claimPrimeReward, 1000);
    setTimeout(() => clickButton(CLOSE_MENU_BUTTON_SELECTOR), 1000);
    setTimeout(() => clickButton(CLOSE_MODAL_BUTTON_SELECTOR), 1000);
    setTimeout(hideGlobalMenu, 1000);
    setTimeout(setupClaimDrops, 1000);

    // Auto refresh drops inventory page
    if (CONFIG.enableAutoRefreshDrops) {
        setInterval(function () {
            if (window.location.href.startsWith('https://www.twitch.tv/drops/inventory')) {
                window.location.reload();
            }
        }, 15 * 60000);
    }

    // Add keyboard shortcut to toggle settings - now using the configured key
    document.addEventListener('keyup', (event) => {
        // Parse the configured key combination
        const parts = CONFIG.settingsKey.split('+');
        const requiredModifiers = {
            Ctrl: parts.includes('Ctrl'),
            Alt: parts.includes('Alt'),
            Shift: parts.includes('Shift'),
            Meta: parts.includes('Meta')
        };

        // The main key is the last part if it's not a modifier
        const mainKey = parts.filter(part => !['Ctrl', 'Alt', 'Shift', 'Meta'].includes(part)).pop();

        // Check if the event matches our configured combination
        const matchesModifiers =
            (!requiredModifiers.Ctrl || event.ctrlKey) &&
            (!requiredModifiers.Alt || event.altKey) &&
            (!requiredModifiers.Shift || event.shiftKey) &&
            (!requiredModifiers.Meta || event.metaKey);

        const matchesMainKey = mainKey ? event.key === mainKey : true;

        if (matchesModifiers && matchesMainKey) {
            // Only trigger on the exact key combination
            if (
                // If Ctrl is in the combination, ensure it's pressed
                (!parts.includes('Ctrl') || event.ctrlKey) &&
                // If Alt is in the combination, ensure it's pressed
                (!parts.includes('Alt') || event.altKey) &&
                // If Shift is in the combination, ensure it's pressed
                (!parts.includes('Shift') || event.shiftKey) &&
                // If Meta is in the combination, ensure it's pressed
                (!parts.includes('Meta') || event.metaKey) &&
                // If a main key is specified, ensure it matches
                (mainKey ? event.key === mainKey : true)
            ) {
                // Prevent default behavior
                event.preventDefault();
                toggleSettingsDialog();

                // Log for debugging
                Logger.info(`${CONFIG.settingsKey} key combination pressed - toggling settings dialog`);
            }
        }
    });

    // Make sure event is captured at the document level with capture phase
    document.addEventListener('keydown', (event) => {
        // Parse the configured key combination
        const parts = CONFIG.settingsKey.split('+');
        const requiredModifiers = {
            Ctrl: parts.includes('Ctrl'),
            Alt: parts.includes('Alt'),
            Shift: parts.includes('Shift'),
            Meta: parts.includes('Meta')
        };

        // The main key is the last part if it's not a modifier
        const mainKey = parts.filter(part => !['Ctrl', 'Alt', 'Shift', 'Meta'].includes(part)).pop();

        // Check if the event matches our configured combination
        const matchesModifiers =
            (!requiredModifiers.Ctrl || event.ctrlKey) &&
            (!requiredModifiers.Alt || event.altKey) &&
            (!requiredModifiers.Shift || event.shiftKey) &&
            (!requiredModifiers.Meta || event.metaKey);

        const matchesMainKey = mainKey ? event.key === mainKey : true;

        if (matchesModifiers && matchesMainKey) {
            // Prevent default behavior for our combination
            event.preventDefault();
        }
    }, true);

    let o = new MutationObserver((m) => {
        if (!CONFIG.enableClaimAllButton && !CONFIG.enableRemoveAllButton) return;

        // Check if the PrimeOfferPopover-header element exists
        const primeOfferHeader = document.getElementById("PrimeOfferPopover-header");
        if (!primeOfferHeader) {
            // If we're on a page where this element doesn't exist, we should stop
            return;
        }

        let script = document.createElement("script");
        script.innerHTML = `
        // Add logger configuration for client-side script
        const Logger = {
            styles: {
                info: 'color: #2196F3; font-weight: bold',
                warning: 'color: #FFC107; font-weight: bold',
                success: 'color: #4CAF50; font-weight: bold',
                error: 'color: #F44336; font-weight: bold'
            },
            prefix: '[TwitchEnhancements]',
            getTimestamp() {
                return new Date().toISOString().split('T')[1].slice(0, -1);
            },
            info(msg) {
                console.log(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.info);
            },
            warning(msg) {
                console.warn(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.warning);
            },
            success(msg) {
                console.log(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.success);
            },
            error(msg) {
                console.error(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.error);
            }
        };

        const openClaimGameTabs = () => {
            // More specific selector targeting only prime offer buttons
            const allButtonTexts = document.querySelectorAll('div[data-a-target="tw-core-button-label-text"]');

            // Filter buttons to only include those with text "Claim Game" or just "Claim"
            const claimGameButtons = Array.from(allButtonTexts).filter(button => {
                const text = button.textContent.trim();
                return (text === "Claim Game" || text === "Claim") &&
                       button.closest('a') && // Must be inside an anchor tag
                       button.closest('.prime-offer'); // Must be inside a prime offer
            });

            Logger.info(\`Found \${claimGameButtons.length} valid claim buttons\`);

            // Open each valid claim button in a new tab
            claimGameButtons.forEach(button => {
                const parentButton = button.closest('a');
                if (parentButton && parentButton.href &&
                    (parentButton.href.includes('gaming.amazon.com') ||
                     parentButton.href.includes('?ingress=twch'))) {
                    window.open(parentButton.href, '_blank');
                }
            });
        };

        const removeClaimedItems = () => {
            // Find ALL items in the list, not just claimed ones
            const allItems = document.querySelectorAll('.prime-offer');
            let dismissedCount = 0;
            let dismissButtons = [];

            Logger.info(\`Found \${allItems.length} total items to dismiss\`);

            // First collect all dismiss buttons - use multiple methods to ensure we catch all
            // Method 1: Find buttons by attribute and data target
            document.querySelectorAll('button[aria-label="Dismiss"][data-a-target="prime-offer-dismiss-button"]').forEach(btn => {
                dismissButtons.push(btn);
            });

            // Method 2: Find buttons by test selector attribute as backup
            document.querySelectorAll('button[data-test-selector="prime-offer-dismiss-button"]').forEach(btn => {
                if (!dismissButtons.includes(btn)) {
                    dismissButtons.push(btn);
                }
            });

            // Method 3: Find by class and structure if the above methods miss any
            document.querySelectorAll('.prime-offer__dismiss button').forEach(btn => {
                if (!dismissButtons.includes(btn)) {
                    dismissButtons.push(btn);
                }
            });

            // Deduplicate just in case
            dismissButtons = [...new Set(dismissButtons)];

            Logger.info(\`Found \${dismissButtons.length} dismiss buttons to click\`);

            // Process dismiss buttons with a delay to avoid UI lockups
            if (dismissButtons.length > 0) {
                const clickNextButton = (index) => {
                    if (index < dismissButtons.length) {
                        try {
                            dismissButtons[index].click();
                            dismissedCount++;

                            // Show progress in console
                            if (dismissedCount % 5 === 0 || dismissedCount === dismissButtons.length) {
                                Logger.info(\`Dismissed \${dismissedCount} of \${dismissButtons.length} items...\`);
                            }
                        } catch (e) {
                            Logger.error(\`Error clicking button \${index}: \` + e);
                        }

                        // Schedule next button click with a small delay
                        setTimeout(() => clickNextButton(index + 1), 75);
                    } else {
                        Logger.success(\`Completed! Dismissed \${dismissedCount} items total.\`);

                        // Look for any dismiss buttons that might have been missed
                        const remainingButtons = document.querySelectorAll('button[aria-label="Dismiss"]');
                        if (remainingButtons.length > 0) {
                            Logger.warning(\`Found \${remainingButtons.length} additional buttons to try\`);

                            // Try to click any remaining dismiss buttons as a final pass
                            remainingButtons.forEach(btn => {
                                try {
                                    btn.click();
                                    dismissedCount++;
                                } catch(e) {}
                            });

                            Logger.success(\`Final dismissal count: \${dismissedCount}\`);
                        }
                    }
                };

                // Start the dismissal process
                clickNextButton(0);
            } else {
                Logger.warning('No dismiss buttons found to click');

                // Last attempt fallback - try to find any button with "Dismiss" in aria-label
                const fallbackButtons = document.querySelectorAll('button[aria-label="Dismiss"]');
                if (fallbackButtons.length > 0) {
                    Logger.warning(\`Fallback: Found \${fallbackButtons.length} buttons with aria-label="Dismiss"\`);
                    fallbackButtons.forEach(btn => {
                        try {
                            btn.click();
                            dismissedCount++;
                        } catch(e) {}
                    });
                    Logger.success(\`Fallback dismissal completed: \${dismissedCount} items dismissed\`);
                }
            }
        };
    `;

        // Safely clear and append to the header
        primeOfferHeader.innerHTML = "";
        primeOfferHeader.appendChild(script);

        if (CONFIG.enableClaimAllButton || CONFIG.enableRemoveAllButton) {
            primeOfferHeader.innerHTML += `
            <div style="display: flex; gap: 10px; margin-bottom: 10px;">
                ${CONFIG.enableClaimAllButton ? `
                <input type='button' style='border: none; background-color: #9147ff; color: white; padding: 10px 20px; font-size: 14px; border-radius: 4px; cursor: pointer; flex: 1;'
                    class='tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-core-button tw-core-button--primary tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative'
                    value='Claim All'
                    onclick='openClaimGameTabs();'>
                ` : ''}
                ${CONFIG.enableRemoveAllButton ? `
                <input type='button' style='border: none; background-color: #772ce8; color: white; padding: 10px 20px; font-size: 14px; border-radius: 4px; cursor: pointer; flex: 1;'
                    class='tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-core-button tw-core-button--primary tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative'
                    value='Remove All'
                    onclick='removeClaimedItems();'>
                ` : ''}
            </div>
        `;
        }
    });

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址