// ==UserScript== // @name YouTube Ultimate Fusion - DEMO TEST // @namespace https://chatgpt.local/ // @version 1.0.0 // @description YouTube / m.youtube / YouTube Music:防自动暂停、自动点继续观看、禁用可见性检测,并集成年龄限制解锁核心 // @author ChatGPT // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @match https://music.youtube.com/* // @match https://www.youtube-nocookie.com/* // @run-at document-start // @grant none // @require https://update.greasyfork.icu/scripts/564478/Simple%20YouTube%20Age%20Restriction%20Bypass.user.js // @downloadURL https://update.greasyfork.icu/scripts/570199/YouTube%20Ultimate%20Fusion%20-%20DEMO%20TEST.user.js // @updateURL https://update.greasyfork.icu/scripts/570199/YouTube%20Ultimate%20Fusion%20-%20DEMO%20TEST.meta.js // ==/UserScript== (() => { 'use strict'; const CONFIG = { debug: false, // 防暂停心跳 keepAlive: true, heartbeatMs: 15000, // 自动扫描 “继续观看 / still watching” autoClickContinue: true, scanMs: 1200, clickCooldownMs: 1500, // 如果因为提示层导致暂停,则尝试恢复播放 autoResumeIfPromptPaused: true, // 伪装页面始终可见 fakeVisibility: true, blockVisibilityListeners: true, }; const HOST = location.host; const IS_MUSIC = HOST === 'music.youtube.com'; const VISIBILITY_DOC_EVENTS = [ 'visibilitychange', 'webkitvisibilitychange', 'mozvisibilitychange', 'msvisibilitychange', ]; const VISIBILITY_WIN_EVENTS = [ 'blur', 'pagehide', 'freeze', ]; const ACTION_TEXT_RE = /(continue watching|still watching|are you still there|video paused|resume|continue playing|keep watching|继续观看|继续播放|继续收听|仍在观看|仍在收听|你还在吗|我还在看|繼續觀看|繼續播放|仍在觀看|還在嗎)/i; const PROMPT_CONTAINER_SELECTORS = [ 'ytmusic-you-there-renderer', '.ytp-popup', '.ytp-error', 'tp-yt-paper-dialog', 'ytd-popup-container', 'yt-confirm-dialog-renderer', 'ytd-enforcement-message-view-model', ].join(','); const BUTTON_SELECTORS = [ 'button', '[role="button"]', 'tp-yt-paper-button', 'yt-button-renderer', 'ytmusic-button-renderer', ].join(','); const clickedMap = new WeakMap(); const log = (...args) => { if (CONFIG.debug) console.log('[YT-Ultimate]', ...args); }; function safeDefine(obj, prop, descriptor) { if (!obj) return false; try { Object.defineProperty(obj, prop, { configurable: true, ...descriptor, }); return true; } catch (e) { return false; } } function isRelevantPage() { return ( IS_MUSIC || location.pathname.startsWith('/watch') || location.pathname.startsWith('/embed/') || location.pathname.startsWith('/shorts/') ); } function textOf(el) { if (!el) return ''; return [ el.textContent || '', el.innerText || '', el.getAttribute?.('aria-label') || '', el.getAttribute?.('title') || '', el.id || '', typeof el.className === 'string' ? el.className : '', ].join(' ').replace(/\s+/g, ' ').trim(); } function isVisible(el) { if (!el || !el.isConnected) return false; const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false; return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length); } function matchesActionText(el) { return ACTION_TEXT_RE.test(textOf(el)); } function getPromptContainers() { const nodes = [...document.querySelectorAll(PROMPT_CONTAINER_SELECTORS)]; return nodes.filter(el => isVisible(el) && ACTION_TEXT_RE.test(textOf(el))); } function findContinueButtons() { const results = []; const seen = new Set(); const containers = getPromptContainers(); const roots = containers.length ? containers : [document]; for (const root of roots) { const candidates = root.querySelectorAll ? [...root.querySelectorAll(BUTTON_SELECTORS)] : []; for (const el of candidates) { if (seen.has(el)) continue; seen.add(el); if (!isVisible(el)) continue; if (!matchesActionText(el)) continue; results.push(el); } } // 兜底:直接搜全页 if (!results.length) { const fallback = [...document.querySelectorAll(BUTTON_SELECTORS)]; for (const el of fallback) { if (seen.has(el)) continue; seen.add(el); if (!isVisible(el)) continue; if (!matchesActionText(el)) continue; results.push(el); } } return results; } function clickLikeUser(el) { if (!el) return false; const last = clickedMap.get(el) || 0; if (Date.now() - last < CONFIG.clickCooldownMs) return false; clickedMap.set(el, Date.now()); try { if (window.PointerEvent) { el.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true })); el.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, cancelable: true })); } el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window })); el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window })); el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch (e) {} try { el.click(); } catch (e) {} log('Clicked continue button:', el); return true; } function hasPromptOnScreen() { if (getPromptContainers().length) return true; return findContinueButtons().length > 0; } async function tryResumePlayback() { if (!CONFIG.autoResumeIfPromptPaused) return false; const video = document.querySelector('video'); if (!video) return false; if (!video.paused) return false; if (!hasPromptOnScreen()) return false; try { await video.play(); log('Playback resumed'); return true; } catch (e) { log('Resume failed', e); return false; } } async function scanAndHandlePrompts() { if (!isRelevantPage()) return; if (CONFIG.autoClickContinue) { const buttons = findContinueButtons(); for (const btn of buttons) { if (clickLikeUser(btn)) { break; } } } await tryResumePlayback(); } function dispatchActivity(target) { if (!target) return; try { target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, cancelable: true, view: window, clientX: 1, clientY: 1, })); } catch (e) {} try { if (window.PointerEvent) { target.dispatchEvent(new PointerEvent('pointermove', { bubbles: true, cancelable: true, clientX: 1, clientY: 1, })); } } catch (e) {} try { target.dispatchEvent(new KeyboardEvent('keydown', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true, cancelable: true, })); target.dispatchEvent(new KeyboardEvent('keyup', { key: 'Shift', code: 'ShiftLeft', keyCode: 16, which: 16, bubbles: true, cancelable: true, })); } catch (e) {} } function keepPlayerAlive() { if (!CONFIG.keepAlive) return; if (!isRelevantPage()) return; const video = document.querySelector('video'); const player = document.querySelector('#movie_player') || document.querySelector('ytmusic-player') || document.querySelector('ytmusic-player-page') || document; dispatchActivity(document); dispatchActivity(player); if (video) dispatchActivity(video); try { window.dispatchEvent(new Event('focus')); } catch (e) {} scanAndHandlePrompts(); } function installVisibilityShield() { if (!CONFIG.fakeVisibility) return; const stop = (e) => { try { e.stopImmediatePropagation(); } catch (err) {} }; for (const type of VISIBILITY_DOC_EVENTS) { document.addEventListener(type, stop, true); } for (const type of VISIBILITY_WIN_EVENTS) { window.addEventListener(type, stop, true); } document.addEventListener('focusout', stop, true); if (CONFIG.blockVisibilityListeners) { const nativeDocAdd = document.addEventListener.bind(document); const nativeWinAdd = window.addEventListener.bind(window); document.addEventListener = function(type, listener, options) { if (VISIBILITY_DOC_EVENTS.includes(type) || type === 'focusout') { log('Blocked document listener:', type); return; } return nativeDocAdd(type, listener, options); }; window.addEventListener = function(type, listener, options) { if (VISIBILITY_WIN_EVENTS.includes(type) || type === 'blur') { log('Blocked window listener:', type); return; } return nativeWinAdd(type, listener, options); }; } const docTargets = [ document, Document.prototype, typeof HTMLDocument !== 'undefined' ? HTMLDocument.prototype : null, ].filter(Boolean); const setDocGetter = (prop, value) => { for (const target of docTargets) { safeDefine(target, prop, { get: () => value }); } }; ['hidden', 'webkitHidden', 'mozHidden', 'msHidden'].forEach(prop => { setDocGetter(prop, false); }); ['visibilityState', 'webkitVisibilityState', 'mozVisibilityState', 'msVisibilityState'].forEach(prop => { setDocGetter(prop, 'visible'); }); for (const target of docTargets) { safeDefine(target, 'hasFocus', { value: () => true }); } safeDefine(document, 'onvisibilitychange', { get: () => null, set: () => true, }); const winTargets = [ window, typeof Window !== 'undefined' ? Window.prototype : null, ].filter(Boolean); for (const target of winTargets) { safeDefine(target, 'onblur', { get: () => null, set: () => true, }); safeDefine(target, 'onpagehide', { get: () => null, set: () => true, }); } log('Visibility shield installed'); } function installObservers() { const start = () => { const root = document.documentElement || document.body; if (!root) return; const mo = new MutationObserver(() => { scanAndHandlePrompts(); }); mo.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style', 'hidden', 'open', 'aria-hidden'], }); const navEvents = [ 'yt-navigate-start', 'yt-navigate-finish', 'yt-page-data-updated', 'spfdone', 'DOMContentLoaded', 'load', 'popstate', ]; for (const type of navEvents) { window.addEventListener(type, () => { setTimeout(() => scanAndHandlePrompts(), 0); setTimeout(() => scanAndHandlePrompts(), 500); setTimeout(() => scanAndHandlePrompts(), 1500); }, true); } setInterval(() => { scanAndHandlePrompts(); }, CONFIG.scanMs); setInterval(() => { keepPlayerAlive(); }, CONFIG.heartbeatMs); scanAndHandlePrompts(); log('Observers installed'); }; if (document.documentElement) { start(); return; } const waitRoot = new MutationObserver(() => { if (document.documentElement) { waitRoot.disconnect(); start(); } }); waitRoot.observe(document, { childList: true, subtree: true }); } installVisibilityShield(); installObservers(); // 可选:暴露配置到控制台,方便你自己在线调试 window.YT_ULTIMATE_CONFIG = CONFIG; })();