// ==UserScript== // @name iYF Enhanced // @name:zh-CN 爱壹帆增强 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 解锁网页端 4K 画质 + 网页全屏 + 画中画 + 去广告 // @author XHXIAIEIN // @match *://*.aiyifan.tv/* // @match *://*.iyf.tv/* // @match *://*.yfsp.tv/* // @match *://*.yifan.tv/* // @match *://*.wyav.tv/* // @icon https://www.google.com/s2/favicons?sz=32&domain=iyf.tv // @license MIT // @grant GM_addStyle // @grant GM_registerMenuCommand // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/572733/iYF%20Enhanced.user.js // @updateURL https://update.greasyfork.icu/scripts/572733/iYF%20Enhanced.meta.js // ==/UserScript== (function () { 'use strict'; // ═════════════════════════════════════════════ // §1 4K 画质解锁 // ═════════════════════════════════════════════ // // 绕过 Angular 质量限制,通过 filter hook 显示 4K 选项, // 点击时拦截 Angular 事件,从 ngContext 取 HLS URL 后 // 直接用 HLS.js 加载 4K 流(3840x1608) const pageScript = document.createElement('script'); pageScript.textContent = `(function(){ var _origFilter = Array.prototype.filter; // ── filter hook: 让 4K 选项显示在质量选择器 UI 中 ── Array.prototype.filter = function(fn, thisArg) { if (this.length > 0 && this[0] && typeof this[0].bitrate === 'number' && 'path' in this[0]) { var has4K = false; for (var j = 0; j < this.length; j++) { if (this[j].bitrate > 1080 && this[j].path) { has4K = true; break; } } if (has4K) { try { if (!fn.call(thisArg, { bitrate: 2160, path: '/t' }, 0, [{ bitrate: 2160, path: '/t' }])) { return _origFilter.call(this, function(item) { return !!item.path; }); } } catch(e) {} } } return _origFilter.call(this, fn, thisArg); }; // ── 移除 "(客户端)" 标签 ── var cleanObserver = new MutationObserver(function() { document.querySelectorAll('.bitrate-text').forEach(function(el) { if (el.textContent.indexOf('客户端') > -1) el.remove(); }); }); if (document.body) cleanObserver.observe(document.body, { childList: true, subtree: true }); else document.addEventListener('DOMContentLoaded', function() { cleanObserver.observe(document.body, { childList: true, subtree: true }); }); // ── 4K 点击劫持:直接用 HLS.js 加载 ── function getVisibleVideo() { var videos = document.querySelectorAll('vg-player video'); for (var i = 0; i < videos.length; i++) { if (videos[i].style.display !== 'none' && videos[i].offsetWidth > 100) return videos[i]; } return null; } function getBitrateData() { var qs = document.querySelector('vg-quality-selector'); if (!qs || !qs.__ngContext__) return null; var ctx = qs.__ngContext__; for (var i = 0; i < Math.min(ctx.length, 60); i++) { var item = ctx[i]; if (item && typeof item === 'object' && item.bitrates && Array.isArray(item.bitrates)) { return item; } } return null; } function findOldHls() { var vgPlayer = document.querySelector('vg-player'); if (!vgPlayer || !vgPlayer.__ngContext__) return null; var ctx = vgPlayer.__ngContext__; for (var i = 0; i < ctx.length; i++) { if (ctx[i] && ctx[i].hls && typeof ctx[i].hls.destroy === 'function') { return ctx[i].hls; } } return null; } function switchToQuality(bitrate) { var comp = getBitrateData(); if (!comp) return false; var target = null; for (var i = 0; i < comp.bitrates.length; i++) { if (comp.bitrates[i].bitrate === bitrate) { target = comp.bitrates[i]; break; } } if (!target || !target.path || !target.path.result) return false; if (typeof Hls === 'undefined') return false; var video = getVisibleVideo(); if (!video) return false; var currentTime = video.currentTime; var wasPlaying = !video.paused; var url = target.path.result; var oldHls = window.__iyf_hls || findOldHls(); if (oldHls) { try { oldHls.destroy(); } catch(e) {} } window.__iyf_hls = null; requestAnimationFrame(function() { var hls = new Hls(); hls.loadSource(url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() { video.currentTime = currentTime; if (wasPlaying) video.play().catch(function(){}); var label = document.querySelector('.vg-quality-selector-label'); if (label) label.textContent = ' ' + (bitrate > 1440 ? '4K' : bitrate + 'P') + ' '; document.querySelectorAll('vg-quality-selector .item').forEach(function(el) { el.classList.remove('active'); var text = el.textContent || ''; if ((bitrate > 1440 && text.indexOf('4K') > -1) || (bitrate <= 1440 && text.indexOf(bitrate + '') > -1)) { el.classList.add('active'); } }); }); hls.on(Hls.Events.ERROR, function(event, data) { if (data.fatal) hls.destroy(); }); window.__iyf_hls = hls; }); return true; } window.__iyf_switchQuality = switchToQuality; window.__iyf_getBitrates = function() { var comp = getBitrateData(); return comp ? comp.bitrates.map(function(b) { return { bitrate: b.bitrate, hasPath: !!b.path, isVIP: b.isVIP }; }) : []; }; // ── 捕获 4K/2K 选项的点击,阻止 Angular 处理 ── document.addEventListener('click', function(e) { var target = e.target; var item = null; for (var d = 0; target && d < 5; d++, target = target.parentElement) { if (target.classList && target.classList.contains('item') && target.closest && target.closest('vg-quality-selector')) { item = target; break; } } if (!item) return; var text = item.textContent || ''; var is4K = text.indexOf('4K') > -1; var is2K = /2[Kk]|1440/.test(text); if (is4K || is2K) { e.stopImmediatePropagation(); e.preventDefault(); switchToQuality(is4K ? 2160 : 1440); } }, true); // ── 自动选择最高画质 ── function autoSelectBest() { var comp = getBitrateData(); if (!comp || !comp.bitrates || comp.bitrates.length === 0) return false; var best = null; for (var i = 0; i < comp.bitrates.length; i++) { var b = comp.bitrates[i]; if (b.path && (!best || b.bitrate > best.bitrate)) best = b; } if (!best) return false; // 检查当前是否已经是最高画质 var label = document.querySelector('.vg-quality-selector-label'); var current = label ? label.textContent.trim() : ''; var bestLabel = best.bitrate > 1440 ? '4K' : best.bitrate + 'P'; if (current.indexOf(bestLabel) > -1) return true; return switchToQuality(best.bitrate); } var _autoTries = 0; var _autoTimer = setInterval(function() { _autoTries++; if (autoSelectBest() || _autoTries > 30) clearInterval(_autoTimer); }, 2000); })();`; (document.head || document.documentElement).appendChild(pageScript); pageScript.remove(); // ═════════════════════════════════════════════ // §2 拦截 "下载客户端" 弹窗(兜底) // ═════════════════════════════════════════════ function blockDownloadDialog() { GM_addStyle(` .cdk-overlay-pane:has(app-ask-app-download-dialog) { display: none !important; } .cdk-global-overlay-wrapper:has(app-ask-app-download-dialog) { display: none !important; } `); const startObserver = () => { new MutationObserver(() => { document.querySelectorAll('.cdk-overlay-pane').forEach(pane => { if (pane.querySelector('app-ask-app-download-dialog')) { const backdrop = pane.parentElement?.previousElementSibling; if (backdrop?.classList?.contains('cdk-overlay-backdrop')) backdrop.click(); pane.style.display = 'none'; } }); }).observe(document.body, { childList: true, subtree: true, }); }; if (document.body) startObserver(); else document.addEventListener('DOMContentLoaded', startObserver); } // ═════════════════════════════════════════════ // §3 样式 // ═════════════════════════════════════════════ const ICONS = { expand: ``, compress: ``, pip: ``, }; const STYLES = ` * { scrollbar-width: thin; scrollbar-color: transparent transparent; } *:hover { scrollbar-color: rgba(255,255,255,0.2) transparent; } ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0); border-radius: 3px; } *:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); } ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.35); } ::-webkit-scrollbar-corner { background: transparent; } html.iyf-web-fs, html.iyf-web-fs body { overflow: hidden !important; margin: 0 !important; padding: 0 !important; } .web-fullscreen { position: fixed !important; inset: 0 !important; width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; border: none !important; z-index: 999999 !important; background: #000 !important; box-sizing: border-box !important; } .web-fullscreen video { width: 100% !important; height: 100% !important; object-fit: contain !important; } .web-fullscreen, .web-fullscreen * { scrollbar-width: none !important; } .web-fullscreen::-webkit-scrollbar, .web-fullscreen *::-webkit-scrollbar { display: none !important; } /* 小窗高度时隐藏侧边功能面板 */ @media (max-height: 950px) { app-sticky-block { display: none !important; } } /* 去广告 */ vg-pause-f .vg-vvk-p { display: none !important; } .dabf { display: none !important; } .ps.pggf { display: none !important; } /* 播放器宽度自适应(仅非悬浮模式) */ .video-player:not(.float-player) { max-width: 100% !important; flex: 1 !important; height: auto !important; } .video-player:not(.float-player) .aa-videoplayer-wrap { height: auto !important; } .video-player:not(.float-player) .video-container:not(.small) { width: 100% !important; height: auto !important; } .video-player:not(.float-player) .video-box { width: 100% !important; aspect-ratio: 16/9 !important; } /* 清除容器固定高度 */ .playPageTop, .main, .video-module, .v-page-content, .d-flex.w-100.justify-content-center { height: auto !important; min-height: 0 !important; } /* VIP 状态下隐藏冗余元素 */ .iyf-is-vip app-dn-user-menu-item:has(.iconVIP), .iyf-is-vip app-daily-sign-in-button, .iyf-is-vip .uploadtable { display: none !important; } .web-fs-btn, .pip-btn { cursor: pointer; padding: 0 8px; color: #fff; display: flex; align-items: center; user-select: none; } .web-fs-btn:hover, .pip-btn:hover { color: #00a1d6; } /* 画中画遮罩 */ .iyf-pip-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); backdrop-filter: blur(12px); z-index: 99999; display: flex; align-items: center; justify-content: center; } .iyf-pip-text { color: rgba(255,255,255,0.6); font-size: 18px; letter-spacing: 2px; } `; // ═════════════════════════════════════════════ // §4 画中画 // ═════════════════════════════════════════════ function getVisibleVideo() { const videos = document.querySelectorAll('vg-player video'); for (const v of videos) { if (v.style.display !== 'none' && v.offsetWidth > 100) return v; } return null; } async function togglePiP() { if (document.pictureInPictureElement) { await document.exitPictureInPicture(); return; } const video = getVisibleVideo(); if (!video) return; video.disablePictureInPicture = false; if (video.readyState < 1) { try { await new Promise((resolve, reject) => { video.addEventListener('loadedmetadata', resolve, { once: true }); video.addEventListener('canplay', resolve, { once: true }); setTimeout(() => reject(new Error('timeout')), 8000); }); } catch (e) { return; } } try { await video.requestPictureInPicture(); const player = document.querySelector('vg-player'); if (player) { const overlay = document.createElement('div'); overlay.className = 'iyf-pip-overlay'; overlay.innerHTML = '