Universal Auto-Scroll (Modified)

Adds auto-scrolling functionality to any website with true dynamic UI scaling and display sleep prevention.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

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

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

(I already have a user script manager, let me install it!)

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

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

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

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

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

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

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Universal Auto-Scroll (Modified)
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Adds auto-scrolling functionality to any website with true dynamic UI scaling and display sleep prevention.
// @author       MasuRii
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Wait a bit for page to be fully ready
    setTimeout(initAutoScroll, 1000);

    function initAutoScroll() {
        // ===================================================================
        // *** USER CUSTOMIZATION: CHANGE THIS VALUE TO RESIZE THE UI PANEL ***
        // 1.0 = default size. 0.8 = 80% size (smaller). 1.2 = 120% size (larger).
        const uiScaleFactor = 0.8;
        // ===================================================================

        // ===================================================================
        // *** USER CUSTOMIZATION: SCROLLING SPEED VALUES ***
        // These are the speed multipliers for the 9 pre-defined speed levels on the slider.
        // These values will be displayed as read-only in the panel.
        const speedMultipliers = [
            0.15, // Speed 1: Snail
            0.25, // Speed 2: Turtle
            0.35, // Speed 3: Leisurely
            0.38, // Speed 4: Relaxed
            0.5,  // Speed 5: Very Slow
            0.75, // Speed 6: Slow
            1.0,  // Speed 7: Medium (Default)
            1.5,  // Speed 8: Fast
            2.5   // Speed 9: Very Fast
        ];
        // ===================================================================

        // Configuration
        const DEFAULT_SETTINGS = {
            isActive: false,
            speed: 7, // Default is "Medium" on the new 1-10 scale
            isMinimized: false,
            pixelsPerSecond: 50,  // Base scrolling speed - will be modified by speed setting
            userSpeedMultiplier: 5.0 // Default value for the user-defined speed level
        };

        // State variables
        let isScrolling = false;
        let scrollIntervalId = null;
        let userScrollDetected = false;
        let userScrollTimeout = null;
        let lastScrollPosition = window.scrollY;
        let wakeLockSentinel = null; // For preventing display sleep

        // Load saved settings or use defaults
        let scrollSettings;
        try {
            const saved = JSON.parse(GM_getValue('autoScrollSettings', '{}'));
            scrollSettings = { ...DEFAULT_SETTINGS, ...saved }; // Merge saved settings with defaults
        } catch (e) {
            console.log('Could not load settings, using defaults');
            scrollSettings = DEFAULT_SETTINGS;
        }

        // DOM elements references
        let container;
        let autoScrollPanel;
        let bubbleView;
        let toggleButton;
        let speedSlider;
        let speedValue;
        let speedValueInput; // NEW: For the speed multiplier input box

        // Adjust panel size using CSS transform to counteract browser zoom
        function adjustPanelForZoom() {
            if (!window.visualViewport || !container) return;
            const browserZoom = window.visualViewport.scale;
            const finalScale = uiScaleFactor / browserZoom;
            container.style.transform = `scale(${finalScale})`;
            container.style.transformOrigin = 'bottom right';
        }

        // Create and inject the UI
        function createUI() {
            const containerId = 'auto-scroll-container-' + Math.random().toString(36).substr(2, 9);
            container = document.createElement('div');
            container.id = containerId;
            document.body.appendChild(container);

            const styleElement = document.createElement('style');
            styleElement.textContent = `
                #${containerId} {
                    all: initial; position: fixed; bottom: 80px; right: 20px; width: 220px; height: auto; z-index: 9999999;
                }
                #${containerId} * {
                    all: unset; box-sizing: border-box; font-family: Arial, sans-serif; color: white;
                }
                #${containerId} .auto-scroll-panel, #${containerId} .auto-scroll-bubble {
                    position: absolute; bottom: 0; right: 0;
                }
                #${containerId} .auto-scroll-panel {
                    background-color: rgba(0, 0, 0, 0.8); padding: 15px; border-radius: 8px; width: 220px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
                    display: ${scrollSettings.isMinimized ? 'none' : 'block'};
                }
                #${containerId} .panel-header {
                    display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.2); padding-bottom: 8px;
                }
                #${containerId} .panel-title { font-weight: bold; font-size: 16px; display: block; }
                #${containerId} .minimize-button { background: none; border: none; cursor: pointer; font-size: 20px; padding: 0; margin: 0; display: block; }
                #${containerId} .section { margin-bottom: 12px; }
                #${containerId} .section-title { font-size: 14px; color: #ffcc00; margin-bottom: 8px; display: block; }
                #${containerId} .toggle-button {
                    background-color: #2196F3; border: none; padding: 10px 15px; text-align: center; font-size: 14px; border-radius: 6px; width: 100%; cursor: pointer;
                    display: flex; justify-content: center; align-items: center; gap: 8px; margin-bottom: 10px;
                }
                #${containerId} .toggle-button.active { background-color: #4CAF50; }
                #${containerId} .slider-container { width: 100%; display: flex; flex-direction: column; gap: 8px; }
                #${containerId} .slider-top { display: flex; justify-content: space-between; width: 100%; font-size: 14px; }
                #${containerId} .speed-value { font-weight: bold; }
                #${containerId} .speed-slider {
                    width: 100%; height: 5px; -webkit-appearance: none; appearance: none; background: #444; outline: none; border-radius: 3px; display: block;
                }
                #${containerId} .speed-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 15px; height: 15px; border-radius: 50%; background: #ffcc00; cursor: pointer; }
                #${containerId} .speed-slider::-moz-range-thumb { width: 15px; height: 15px; border-radius: 50%; background: #ffcc00; cursor: pointer; }
                #${containerId} .slider-labels { display: flex; justify-content: space-between; width: 100%; font-size: 12px; }
                #${containerId} .auto-scroll-bubble {
                    width: 60px; height: 60px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.8); display: ${scrollSettings.isMinimized ? 'flex' : 'none'};
                    flex-direction: column; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); font-size: 24px;
                }
                /* NEW STYLES for speed value input */
                #${containerId} .speed-value-container { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 14px; }
                #${containerId} .speed-value-input {
                    width: 60px; background-color: #333; border: 1px solid #555; border-radius: 4px; padding: 4px 8px; text-align: center; font-size: 14px;
                }
                #${containerId} .speed-value-input:disabled { background-color: #222; color: #888; cursor: not-allowed; }
            `;
            document.head.appendChild(styleElement);

            autoScrollPanel = document.createElement('div');
            autoScrollPanel.className = 'auto-scroll-panel';
            container.appendChild(autoScrollPanel);

            // ... (Panel Header and Status Section are unchanged)
            const panelHeader = document.createElement('div');
            panelHeader.className = 'panel-header';
            const panelTitle = document.createElement('div');
            panelTitle.className = 'panel-title';
            panelTitle.textContent = 'Auto-Scroll Controls';
            const minimizeButton = document.createElement('button');
            minimizeButton.className = 'minimize-button';
            minimizeButton.textContent = '−';
            minimizeButton.addEventListener('click', toggleMinimize);
            panelHeader.appendChild(panelTitle);
            panelHeader.appendChild(minimizeButton);
            autoScrollPanel.appendChild(panelHeader);

            const statusSection = document.createElement('div');
            statusSection.className = 'section';
            const statusTitle = document.createElement('div');
            statusTitle.className = 'section-title';
            statusTitle.textContent = 'Scrolling Status';
            toggleButton = document.createElement('button');
            toggleButton.className = 'toggle-button' + (scrollSettings.isActive ? ' active' : '');
            toggleButton.innerHTML = scrollSettings.isActive ? '<span>⏸️</span> Pause Scrolling' : '<span>▶️</span> Start Scrolling';
            toggleButton.addEventListener('click', toggleScrolling);
            statusSection.appendChild(statusTitle);
            statusSection.appendChild(toggleButton);
            autoScrollPanel.appendChild(statusSection);


            // --- Speed Section (Modified) ---
            const speedSection = document.createElement('div');
            speedSection.className = 'section';
            const speedTitle = document.createElement('div');
            speedTitle.className = 'section-title';
            speedTitle.textContent = 'Scroll Speed';
            const sliderContainer = document.createElement('div');
            sliderContainer.className = 'slider-container';
            const sliderTop = document.createElement('div');
            sliderTop.className = 'slider-top';
            const speedLabel = document.createElement('span');
            speedLabel.textContent = 'Speed:';
            speedValue = document.createElement('span');
            speedValue.className = 'speed-value';
            sliderTop.appendChild(speedLabel);
            sliderTop.appendChild(speedValue);
            speedSlider = document.createElement('input');
            speedSlider.type = 'range';
            speedSlider.className = 'speed-slider';
            speedSlider.min = '1';
            speedSlider.max = '10'; // Increased max to 10 for custom speed
            speedSlider.value = scrollSettings.speed;
            speedSlider.addEventListener('input', handleSpeedChange);
            const sliderLabels = document.createElement('div');
            sliderLabels.className = 'slider-labels';
            const slowLabel = document.createElement('span');
            slowLabel.textContent = 'Slow';
            const fastLabel = document.createElement('span');
            fastLabel.textContent = 'Fast';
            sliderLabels.appendChild(slowLabel);
            sliderLabels.appendChild(fastLabel);
            sliderContainer.appendChild(sliderTop);
            sliderContainer.appendChild(speedSlider);
            sliderContainer.appendChild(sliderLabels);
            speedSection.appendChild(speedTitle);
            speedSection.appendChild(sliderContainer);

            // --- NEW: Speed Value Input Section ---
            const speedValueContainer = document.createElement('div');
            speedValueContainer.className = 'speed-value-container';
            const speedValueLabel = document.createElement('span');
            speedValueLabel.textContent = 'Speed Multiplier:';
            speedValueInput = document.createElement('input');
            speedValueInput.type = 'number';
            speedValueInput.className = 'speed-value-input';
            speedValueInput.step = '0.1';
            speedValueInput.min = '0.1';
            speedValueInput.addEventListener('input', handleUserSpeedInputChange);
            speedValueContainer.appendChild(speedValueLabel);
            speedValueContainer.appendChild(speedValueInput);
            speedSection.appendChild(speedValueContainer);

            autoScrollPanel.appendChild(speedSection);

            // ... (Bubble View is unchanged)
            bubbleView = document.createElement('div');
            bubbleView.className = 'auto-scroll-bubble';
            bubbleView.innerHTML = '📜';
            bubbleView.addEventListener('click', toggleMinimize);
            container.appendChild(bubbleView);

            // --- Initial UI State Setup ---
            updateSpeedDisplay(scrollSettings.speed);
            updateSpeedValueInputState(scrollSettings.speed);
            if (scrollSettings.isActive) {
                startScrolling();
            }
        }

        function toggleMinimize() {
            scrollSettings.isMinimized = !scrollSettings.isMinimized;
            autoScrollPanel.style.display = scrollSettings.isMinimized ? 'none' : 'block';
            bubbleView.style.display = scrollSettings.isMinimized ? 'flex' : 'none';
            saveSettings();
        }

        function toggleScrolling() {
            scrollSettings.isActive = !scrollSettings.isActive;
            if (scrollSettings.isActive) {
                toggleButton.classList.add('active');
                toggleButton.innerHTML = '<span>⏸️</span> Pause Scrolling';
                startScrolling();
            } else {
                toggleButton.classList.remove('active');
                toggleButton.innerHTML = '<span>▶️</span> Start Scrolling';
                stopScrolling();
            }
            saveSettings();
        }

        function handleSpeedChange() {
            const value = parseInt(speedSlider.value);
            scrollSettings.speed = value;
            updateSpeedDisplay(value);
            updateSpeedValueInputState(value); // NEW: Update the input box state
            if (isScrolling) {
                stopScrolling();
                startScrolling();
            }
            saveSettings();
        }

        // NEW: Handle user input in the custom speed box
        function handleUserSpeedInputChange() {
            const newSpeed = parseFloat(speedValueInput.value);
            if (!isNaN(newSpeed) && newSpeed > 0) {
                scrollSettings.userSpeedMultiplier = newSpeed;
                saveSettings();
                if (isScrolling) {
                    stopScrolling();
                    startScrolling();
                }
            }
        }

        function updateSpeedDisplay(value) {
            let speedText;
            if (value === 1) speedText = "Snail";
            else if (value === 2) speedText = "Turtle";
            else if (value === 3) speedText = "Leisurely";
            else if (value === 4) speedText = "Relaxed";
            else if (value === 5) speedText = "Very Slow";
            else if (value === 6) speedText = "Slow";
            else if (value === 7) speedText = "Medium";
            else if (value === 8) speedText = "Fast";
            else if (value === 9) speedText = "Very Fast";
            else speedText = "Custom"; // Level 10
            speedValue.textContent = `${speedText} (${value})`;
        }

        // NEW: Update the state (value and disabled status) of the speed multiplier input box
        function updateSpeedValueInputState(value) {
            if (value < 10) {
                speedValueInput.value = speedMultipliers[value - 1];
                speedValueInput.disabled = true;
            } else { // value is 10 (Custom)
                speedValueInput.value = scrollSettings.userSpeedMultiplier;
                speedValueInput.disabled = false;
            }
        }

        async function requestWakeLock() {
            if ('wakeLock' in navigator && wakeLockSentinel === null) {
                try {
                    wakeLockSentinel = await navigator.wakeLock.request('screen');
                    wakeLockSentinel.addEventListener('release', () => { wakeLockSentinel = null; });
                } catch (err) {
                    console.error(`Auto-Scroll Wake Lock Error: ${err.name}, ${err.message}`);
                }
            }
        }

        async function releaseWakeLock() {
            if (wakeLockSentinel !== null) {
                await wakeLockSentinel.release();
                wakeLockSentinel = null;
            }
        }

        function startScrolling() {
            if (isScrolling) return;
            isScrolling = true;
            lastScrollPosition = window.scrollY;
            requestWakeLock();

            let currentMultiplier;
            if (scrollSettings.speed < 10) {
                currentMultiplier = speedMultipliers[scrollSettings.speed - 1];
            } else {
                currentMultiplier = scrollSettings.userSpeedMultiplier;
            }

            const pixelsPerSecond = DEFAULT_SETTINGS.pixelsPerSecond * currentMultiplier;
            const scrollDelay = Math.floor(1000 / pixelsPerSecond);
            scrollIntervalId = setInterval(() => {
                if (!userScrollDetected) {
                    window.scrollBy(0, 1);
                }
            }, scrollDelay);
        }

        function stopScrolling() {
            if (!isScrolling) return;
            isScrolling = false;
            releaseWakeLock();
            if (scrollIntervalId !== null) {
                clearInterval(scrollIntervalId);
                scrollIntervalId = null;
            }
        }

        function saveSettings() {
            try {
                GM_setValue('autoScrollSettings', JSON.stringify(scrollSettings));
            } catch (e) {
                console.error('Failed to save settings:', e);
            }
        }

        function handleUserScroll() {
            const currentPosition = window.scrollY;
            if (isScrolling && Math.abs(currentPosition - lastScrollPosition) > 3) {
                userScrollDetected = true;
                if (userScrollTimeout) clearTimeout(userScrollTimeout);
                userScrollTimeout = setTimeout(() => {
                    userScrollDetected = false;
                    lastScrollPosition = window.scrollY;
                }, 5000);
            }
            lastScrollPosition = currentPosition;
        }

        function handleVisibilityChange() {
            if (document.visibilityState === 'visible' && isScrolling) {
                requestWakeLock();
            }
        }

        if (document.body && document.body.scrollHeight > window.innerHeight) {
            createUI();
            if (window.visualViewport) {
                adjustPanelForZoom(); // Initial adjustment
                window.visualViewport.addEventListener('resize', adjustPanelForZoom);
            }
            window.addEventListener('scroll', handleUserScroll, { passive: true });
            document.addEventListener('visibilitychange', handleVisibilityChange);
            window.addEventListener('beforeunload', () => {
                stopScrolling();
                if (window.visualViewport) {
                    window.visualViewport.removeEventListener('resize', adjustPanelForZoom);
                }
                document.removeEventListener('visibilitychange', handleVisibilityChange);
            });
        }
    }
})();