Pornhub Auto Next & CSS Fullscreen

Automatically enters CSS web fullscreen on video load and clicks 'next' on video end. Toggle fullscreen with 'G' key.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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         Pornhub Auto Next & CSS Fullscreen
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Automatically enters CSS web fullscreen on video load and clicks 'next' on video end. Toggle fullscreen with 'G' key.
// @author       CurssedCoffin (by gemini) https://github.com/CurssedCoffin
// @match        *://*.pornhub.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pornhub.com
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const LOG_PREFIX = '[PH Auto FS/Next & Bypass] ';
    const NEXT_BUTTON_SELECTOR = '.mgp_nextBtn';
    const MTUBES_BUTTON_SELECTOR = '#modalWrapMTubes button';
    const MTUBES_MODAL_SELECTOR = '#modalWrapMTubes';

    const PLAYER_QUALIFYING_SELECTORS = 'video.mgp_videoElement';
    const PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN = '.video-element-wrapper-js';

    const FULLSCREEN_STATE_STORAGE_KEY = 'phWebFullscreenStateManualSave'; 

    const MAX_RETRIES_ENTER_FULLSCREEN_VIDEO_SEARCH = 30; 
    const RETRY_INTERVAL_ENTER_FULLSCREEN_VIDEO_SEARCH = 200; 

    const MAX_RETRIES_AUTO_NEXT_CLICK = MAX_RETRIES_ENTER_FULLSCREEN_VIDEO_SEARCH;
    const RETRY_INTERVAL_AUTO_NEXT_CLICK = RETRY_INTERVAL_ENTER_FULLSCREEN_VIDEO_SEARCH;

    let webFullscreenApplied = false;
    let videoElementCache = null; 
    let initialFullscreenAttempted = false; 

    let mainVideoElementObserver = null; 
    let mtubesInterval = null; 
    let isBypassingMTubes = false; 

    const webFullscreenCSS = `
        body.ph-web-fullscreen-active,
        html.ph-web-fullscreen-active {
            overflow: hidden !important;
        }
        .ph-player-is-web-fullscreen {
            position: fixed !important; top: 0 !important; left: 0 !important;
            width: 100vw !important; height: 100vh !important;
            z-index: 2147483646 !important; background-color: black !important;
            padding: 0 !important; margin: 0 !important; border: none !important;
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
        }
        .ph-player-is-web-fullscreen video {
            width: 100% !important; height: 100% !important; object-fit: contain !important;
            max-width: 100vw !important; max-height: 100vh !important;
            z-index: 1 !important;
        }
        .ph-player-is-web-fullscreen video.mgp_videoElement {
            position: absolute !important;
            left: 0px !important;
            top: 0px !important;
            width: 100% !important;
            height: 100% !important;
            object-fit: contain !important;
        }
        .ph-player-is-web-fullscreen video.fp-player {
            position: absolute !important;
            left: 0px !important;
            top: 0px !important;
            width: 100% !important;
            height: 100% !important;
            object-fit: contain !important;
        }
         .ph-player-is-web-fullscreen .mgp_controlsContainer,
        .ph-player-is-web-fullscreen .mgp_controlsBar,
        .ph-player-is-web-fullscreen .fp-ui,
        .ph-player-is-web-fullscreen .fp-controls,
        .ph-player-is-web-fullscreen .video-control-container {
            z-index: 2147483647 !important;
            pointer-events: auto !important;
            opacity: 1 !important; visibility: visible !important;
            position: absolute !important;
            bottom: 0 !important;
            left: 0 !important;
            width: 100% !important;
        }
        .ph-player-is-web-fullscreen .mgp_controlsContainer *,
        .ph-player-is-web-fullscreen .mgp_controlsBar *,
        .ph-player-is-web-fullscreen .fp-ui *,
        .ph-player-is-web-fullscreen .fp-controls * {
            pointer-events: auto !important;
        }
        body.ph-web-fullscreen-active #header,
        body.ph-web-fullscreen-active #main-container > .container:not(:has(.ph-player-is-web-fullscreen)),
        body.ph-web-fullscreen-active #footer,
        body.ph-web-fullscreen-active .bottomMenu,
        body.ph-web-fullscreen-active #relatedVideosCenter,
        body.ph-web-fullscreen-active #comments,
        body.ph-web-fullscreen-active .rightCol,
        body.ph-web-fullscreen-active .leftCol,
        body.ph-web-fullscreen-active .abovePlayer,
        body.ph-web-fullscreen-active .belowPlayer,
        body.ph-web-fullscreen-active #hd-rightColVideoPage,
        body.ph-web-fullscreen-active .wrapper #sb_wrapper {
            display: none !important;
        }
    `;

    function addCustomCSS() {
        if (typeof GM_addStyle !== "undefined") { GM_addStyle(webFullscreenCSS); }
        else {
            const styleSheet = document.createElement("style");
            styleSheet.type = "text/css"; styleSheet.innerText = webFullscreenCSS;
            document.head.appendChild(styleSheet);
        }
        console.log(LOG_PREFIX + 'Web fullscreen CSS injected.');
    }
    addCustomCSS();

    // Helper: Safely checks if an element is currently visible on screen
    function isElementVisible(el) {
        if (!el) return false;
        const style = getComputedStyle(el);
        return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetParent !== null;
    }

    function findVisibleElement(selector) {
        const element = document.querySelector(selector);
        if (isElementVisible(element)) {
            let parent = element.parentElement;
            while (parent && parent !== document.body) {
                const parentStyle = getComputedStyle(parent);
                if (parentStyle.display === 'none' || parentStyle.visibility === 'hidden') return null;
                parent = parent.parentElement;
            }
            return element;
        }
        return null;
    }

    function findVideoElement() {
        if (videoElementCache && document.body.contains(videoElementCache)) {
            return videoElementCache;
        }

        try {
            let video = document.querySelector(PLAYER_QUALIFYING_SELECTORS);
            if (video) {
                 videoElementCache = video;
                 return video;
            }
        } catch (e) {}

        let videos = Array.from(document.querySelectorAll('video'));
        videos = videos.filter(v => v.readyState > 0 && v.duration > 0 && v.videoWidth > 5 && v.videoHeight > 5);
        if (videos.length > 0) {
            videos.sort((a, b) => (b.videoWidth * b.videoHeight) - (a.videoWidth * a.videoHeight));
            const mainVideoInPlayer = videos.find(v => v.closest(PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN));
            if (mainVideoInPlayer) {
                videoElementCache = mainVideoInPlayer;
                return mainVideoInPlayer;
            }
            if (videos[0].closest('body')) { 
                 videoElementCache = videos[0];
                 return videos[0];
            }
        }

        videoElementCache = null; 
        return null;
    }

    function simulateDetailedClick(element) {
        if (!element) return;
        try {
            const LER = element.getBoundingClientRect();
            // Do not click if bounding rect is 0 (hidden)
            if (LER.width === 0 || LER.height === 0) return;
            
            const elementWindow = element.ownerDocument.defaultView || window;
            const eventArgs = { bubbles: true, cancelable: true, view: elementWindow, button: 0, clientX: LER.left + (LER.width / 2), clientY: LER.top + (LER.height / 2) };
            element.dispatchEvent(new PointerEvent('pointerdown', eventArgs));
            element.dispatchEvent(new MouseEvent('mousedown', eventArgs));
            element.dispatchEvent(new PointerEvent('pointerup', eventArgs));
            element.dispatchEvent(new MouseEvent('mouseup', eventArgs));
            element.dispatchEvent(new MouseEvent('click', eventArgs));
            if (typeof element.click === 'function') element.click();
        } catch (e) { console.error(LOG_PREFIX + 'Error during click simulation:', e); }
    }

    function clearInlineStyles(element) {
         if (!element) return;
         const stylesToClear =['width', 'height', 'objectFit', 'position', 'zIndex', 'maxWidth', 'maxHeight', 'left', 'top', 'margin', 'padding', 'border'];
         stylesToClear.forEach(prop => element.style[prop] = '');
    }

    function setFullscreenState(isFullScreen) {
        try {
            GM_setValue(FULLSCREEN_STATE_STORAGE_KEY, isFullScreen);
            console.log(LOG_PREFIX + `Fullscreen state saved: ${isFullScreen}`);
        } catch (e) {}
    }

    function getFullscreenState() {
        try {
            return GM_getValue(FULLSCREEN_STATE_STORAGE_KEY, false);
        } catch (e) {
            return false;
        }
    }

    function enterWebFullscreen(retryAttempt = 0) {
        if (webFullscreenApplied || (retryAttempt >= MAX_RETRIES_ENTER_FULLSCREEN_VIDEO_SEARCH)) {
             if (webFullscreenApplied) console.log(LOG_PREFIX + 'Already in web fullscreen, not re-applying.');
             return webFullscreenApplied;
        }

        const videoElement = findVideoElement();

        if (!videoElement) {
             setTimeout(() => enterWebFullscreen(retryAttempt + 1), RETRY_INTERVAL_ENTER_FULLSCREEN_VIDEO_SEARCH);
             return false;
        }

        let playerContainer = videoElement.closest(PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN);

        if (!playerContainer && retryAttempt < MAX_RETRIES_ENTER_FULLSCREEN_VIDEO_SEARCH) {
             setTimeout(() => enterWebFullscreen(retryAttempt + 1), RETRY_INTERVAL_ENTER_FULLSCREEN_VIDEO_SEARCH);
             return false;
        }

        if (playerContainer) {
            console.log(LOG_PREFIX + 'Entering web fullscreen...');
            clearInlineStyles(playerContainer); clearInlineStyles(videoElement);
            document.documentElement.classList.add('ph-web-fullscreen-active');
            document.body.classList.add('ph-web-fullscreen-active');
            playerContainer.classList.add('ph-player-is-web-fullscreen');

            if (videoElement.classList.contains('mgp_videoElement') || videoElement.classList.contains('fp-player')) {
                 videoElement.style.left = '0px'; videoElement.style.top = '0px';
                 videoElement.style.position = 'absolute'; videoElement.style.width = '100%';
                 videoElement.style.height = '100%'; videoElement.style.objectFit = 'contain';
            }
            webFullscreenApplied = true;
            console.log(LOG_PREFIX + 'Web fullscreen applied successfully.');
            if (typeof window.dispatchEvent === 'function') window.dispatchEvent(new Event('resize'));
            
            const playerInstance = videoElement.player || playerContainer.player || (window.player && typeof window.player.resize === 'function' ? window.player : null);
            if (playerInstance && typeof playerInstance.resize === 'function') {
                try { playerInstance.resize(); } catch (e) {}
            }
            return true;
        } else {
             return false;
        }
    }

    function exitWebFullscreen() {
        if (!webFullscreenApplied && !document.querySelector('.ph-player-is-web-fullscreen')) {
             return true;
        }
        console.log(LOG_PREFIX + 'Exiting web fullscreen.');
        document.documentElement.classList.remove('ph-web-fullscreen-active');
        document.body.classList.remove('ph-web-fullscreen-active');
        const playerContainer = document.querySelector('.ph-player-is-web-fullscreen');
        if (playerContainer) {
            playerContainer.classList.remove('ph-player-is-web-fullscreen');
            const videoElement = playerContainer.querySelector('video');
            if (videoElement) clearInlineStyles(videoElement);
            clearInlineStyles(playerContainer);
        }
        const currentVideoElement = findVideoElement(); 
        if (currentVideoElement && (!playerContainer || !playerContainer.contains(currentVideoElement))) {
             clearInlineStyles(currentVideoElement);
        }
        webFullscreenApplied = false;
        if (typeof window.dispatchEvent === 'function') window.dispatchEvent(new Event('resize'));
        
        const videoForResize = currentVideoElement || (playerContainer ? playerContainer.querySelector('video') : null);
        if (videoForResize) {
            const playerInstance = videoForResize.player || (videoForResize.closest(PLAYER_QUALIFYING_SELECTORS) ? (videoForResize.closest(PLAYER_QUALIFYING_SELECTORS).player || window.player) : window.player) ;
            if (playerInstance && typeof playerInstance.resize === 'function') {
                try { playerInstance.resize(); } catch (e) {}
            }
        }
        return true;
    }

    function toggleWebFullscreenAndSaveState() {
        if (webFullscreenApplied && document.querySelector('.ph-player-is-web-fullscreen')) {
            exitWebFullscreen(); setFullscreenState(false);
        } else {
            const entered = enterWebFullscreen(); if (entered) setFullscreenState(true);
        }
    }

    function handleKeyDown(event) {
        if (event.key.toLowerCase() === 'g' && !/INPUT|TEXTAREA|SELECT|BUTTON/.test(event.target.tagName) && !event.target.isContentEditable) {
            event.preventDefault(); event.stopPropagation();
            console.log(LOG_PREFIX + "'G' key pressed. Toggling web fullscreen and saving state.");
            toggleWebFullscreenAndSaveState();
        }
    }
    document.addEventListener('keydown', handleKeyDown, true);

    function clickNextButtonWithRetries(retryAttempt = 0) {
        if (retryAttempt >= MAX_RETRIES_AUTO_NEXT_CLICK) return;
        
        const nextButton = findVisibleElement(NEXT_BUTTON_SELECTOR);
        if (nextButton) {
            console.log(LOG_PREFIX + 'Primary next button found, clicking.');
            simulateDetailedClick(nextButton);
        } else {
            const alternateNextSelectors =['.upNextPlayer', 'a[rel="next"]', '[data-action="next-video"]', '.recommended-video-link:first-child', '.mgp_popUpNextVideoInfo a', '.icon-Next'];
            let alternateButton = alternateNextSelectors.reduce((found, sel) => found || findVisibleElement(sel), null);
            if (alternateButton) {
                simulateDetailedClick(alternateButton);
            } else {
                setTimeout(() => clickNextButtonWithRetries(retryAttempt + 1), RETRY_INTERVAL_AUTO_NEXT_CLICK);
            }
        }
    }

    // ==========================================
    // MTubes Auto Bypass & Bulletproof UI Play Clicker
    // ==========================================
    function handleMTubesBypass() {
        const button = document.querySelector(MTUBES_BUTTON_SELECTOR);
        
        if (button && isElementVisible(button) && !button.disabled) {
            if (isBypassingMTubes) return; 
            isBypassingMTubes = true;
            
            console.log(LOG_PREFIX + 'MTubes modal detected. Triggering detailed bypass click...');
            simulateDetailedClick(button); 
            
            let resumeChecks = 0;
            const resumeInterval = setInterval(() => {
                resumeChecks++;
                
                // Allow up to 15 seconds (30 attempts)
                if (resumeChecks > 30) {
                    clearInterval(resumeInterval);
                    isBypassingMTubes = false;
                    console.log(LOG_PREFIX + 'Resume timeout reached. Stopped forcing playback.');
                    return;
                }

                // 1. Wait for the verification modal (spinner) to completely disappear
                const modal = document.querySelector(MTUBES_MODAL_SELECTOR);
                if (isElementVisible(modal)) {
                    // Still loading/spinning, do not click play yet.
                    return;
                }

                // 2. Locate the specific .mgp_playIcon that indicates a paused UI state
                const playIcon = document.querySelector('.mgp_playIcon');
                const playbackBtnContainer = document.querySelector('.mgp_playback') || playIcon;
                const videoElem = findVideoElement();
                
                // Check if the icon is physically visible on the screen. 
                // Custom players hide the Play icon and show the Pause icon when playing.
                const isUIWaitingForClick = isElementVisible(playIcon);

                // If the UI Play Icon is visible, or the raw video element is paused
                if (isUIWaitingForClick || (videoElem && videoElem.paused)) {
                    console.log(LOG_PREFIX + `UI shows paused (Play icon visible). Clicking it! (Attempt ${resumeChecks})`);
                    
                    // Directly target the elements you specified
                    if (playIcon) simulateDetailedClick(playIcon);
                    if (playbackBtnContainer) simulateDetailedClick(playbackBtnContainer);
                    
                    // Fallback HTML5 API trigger just in case
                    if (videoElem && videoElem.paused) {
                        videoElem.play().catch(e => {});
                    }
                } else if (!isUIWaitingForClick && videoElem && !videoElem.paused) {
                    // Both the UI and the Video Element agree that it is playing!
                    console.log(LOG_PREFIX + 'Play icon disappeared and video is playing! Routine finished.');
                    clearInterval(resumeInterval);
                    
                    // Reset flag after a short delay
                    setTimeout(() => { isBypassingMTubes = false; }, 1000);
                }

            }, 500); 
        }
    }


    // ==========================================
    // Video Main Loop & Event Listeners
    // ==========================================
    function attachListenersToFoundVideo(videoElement) {
        if (!initialFullscreenAttempted) {
            initialFullscreenAttempted = true;
            if (getFullscreenState()) {
                console.log(LOG_PREFIX + 'Persistent fullscreen state is true. Attempting to enter fullscreen.');
                setTimeout(() => enterWebFullscreen(), 300);
            }
        }

        if (videoElement.dataset.autoNextListenerAttached !== 'true') {
            videoElement.addEventListener('ended', function onVideoEnded() {
                console.log(LOG_PREFIX + 'Video ended. Clicking next...');
                setTimeout(() => {
                    clickNextButtonWithRetries();
                }, 800);
            });
            videoElement.dataset.autoNextListenerAttached = 'true';
            console.log(LOG_PREFIX + 'Auto-next event listener attached to:', videoElement);
        }
    }

    function tryAttachVideoListeners() {
        const videoElement = findVideoElement();

        if (videoElement) {
            if (videoElement.readyState >= 1 || !videoElement.paused || videoElement.src || videoElement.HAVE_CURRENT_DATA >=1 ) {
                attachListenersToFoundVideo(videoElement);
            }
        } else {
            if (!initialFullscreenAttempted && getFullscreenState()) {
                initialFullscreenAttempted = true;
                setTimeout(() => enterWebFullscreen(), 300);
            }
        }
    }

    function initializeMainVideoObserver() {
        if (mainVideoElementObserver) {
            mainVideoElementObserver.disconnect();
        }

        mainVideoElementObserver = new MutationObserver((mutationsList) => {
            let potentiallyRelevantChange = false;
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    const hasVideoNode = (nodes) => Array.from(nodes).some(node =>
                        node.nodeName === 'VIDEO' ||
                        (node.matches && (node.matches(PLAYER_QUALIFYING_SELECTORS) || node.matches(PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN))) ||
                        (node.querySelector && (node.querySelector(PLAYER_QUALIFYING_SELECTORS) || node.querySelector(PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN)))
                    );
                    if (hasVideoNode(mutation.addedNodes) || hasVideoNode(mutation.removedNodes)) {
                        potentiallyRelevantChange = true;
                        break;
                    }
                } else if (mutation.type === 'attributes') {
                    if (mutation.target.nodeName === 'VIDEO' && (mutation.attributeName === 'src' || mutation.attributeName === 'id' || mutation.attributeName === 'class')) {
                        potentiallyRelevantChange = true;
                        break;
                    }
                    if (mutation.target.matches && mutation.target.matches(PLAYER_CONTAINER_SELECTORS_FOR_FULLSCREEN) && (mutation.attributeName === 'class' || mutation.attributeName === 'style')) {
                         potentiallyRelevantChange = true;
                         break;
                    }
                }
            }

            if (potentiallyRelevantChange) {
                tryAttachVideoListeners();
            }
        });

        mainVideoElementObserver.observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
        });

        setTimeout(tryAttachVideoListeners, 250);
        setTimeout(tryAttachVideoListeners, 1000);
        setTimeout(tryAttachVideoListeners, 3000);
    }

    const nextButtonObserver = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'attributes' && mutation.target.matches && mutation.target.matches(NEXT_BUTTON_SELECTOR)) {
                 const videoElem = findVideoElement();
                 if (videoElem && videoElem.ended && !document.querySelector('.mgp_nextBtn:focus')) {
                     setTimeout(() => {
                        const nextBtn = findVisibleElement(NEXT_BUTTON_SELECTOR);
                        if(nextBtn) simulateDetailedClick(nextBtn);
                     }, 250);
                 }
            }
        }
    });
    nextButtonObserver.observe(document.body, { attributes: true, subtree: true, attributeFilter:['style', 'class', 'href'] });


    // ==========================================
    // Startup and Resource Cleanup
    // ==========================================

    initializeMainVideoObserver();

    handleMTubesBypass(); 
    mtubesInterval = setInterval(handleMTubesBypass, 1500); 
    window.addEventListener('resize', handleMTubesBypass);
    window.addEventListener('scroll', handleMTubesBypass);


    window.addEventListener('beforeunload', () => {
        document.removeEventListener('keydown', handleKeyDown, true);
        
        clearInterval(mtubesInterval);
        window.removeEventListener('resize', handleMTubesBypass);
        window.removeEventListener('scroll', handleMTubesBypass);
        
        if (webFullscreenApplied) {
             exitWebFullscreen();
        }
        nextButtonObserver.disconnect();
        if (mainVideoElementObserver) {
            mainVideoElementObserver.disconnect();
        }
        console.log(LOG_PREFIX + 'Cleaned up listeners and observers.');
    });

})();