Automatically enters CSS web fullscreen on video load and clicks 'next' on video end. Toggle fullscreen with 'G' key.
// ==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.');
});
})();