// ==UserScript== // @name 大魔王视频助手<移动版> // @namespace http://tampermonkey.net/ // @version 1.1 // @description 悬浮播放器显示、识别横屏与竖屏视频显示、可拖拽位置并且记忆位置,自动接管播放网页视频,长按倍速播放支持,双击切换全屏,双指长按弹出控制面板。手势调节播放进度、亮度、声音,精准智能去除m3u8广告片段,强力分析诊断工具。 // @author bug大魔王 // @match *://*/* // @connect * // @require https://unpkg.com/hls.js@1.5.12/dist/hls.min.js // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_info // @run-at document-start // @icon https://n.sinaimg.cn/sinacn10/576/w700h676/20181001/87c2-hkvrhpr8368697.jpg // ==/UserScript== (function () { 'use strict'; const Hls = window.Hls, SCRIPT_NAME = '大魔王视频助手', SCRIPT_VERSION = '1.0', IS_TOP_FRAME = window.self === window.top; const MSG = { M3U8: 'M3U8_PLAYER_COMMAND_V2_FINAL', SANDBOX: 'SANDBOX_URL_FOUND_V1', RAW: 'RAW_SIGNAL_FORWARDED_V1', LOG: 'ACTION_LOG_FORWARDED_V1', NEUTRALIZE: 'DMZ_NEUTRALIZE_COMMAND_V1', AUTOPLAY: 'DMZ_ACTIVATE_AUTOPLAY_V1', QUERY: 'DMZ_QUERY_IS_MAIN_PLAYER_V1', CLICK: 'DMZ_IFRAME_TRIGGER_CLICK_V1', RESTORE: 'DMZ_RESTORE_COMMAND_V1' }; const C = { ROOT: 'dmz-host-container', PLAYER: 'dmz-custom-player', SETTINGS: 'dmz-settings-panel', INFO: 'dmz-info-panel', MAX_DEPTH: 5, H_PX: '36px', WRAP: 'dmz-video-wrapper', CLOSE: 'dmz-close-button', DRAG: 'dmz-drag-handle', SCREW: 'screw-effect', IND: 'indicator', STYLE: 'dmz-player-styles', GRID: 'settings-grid', CARD: 'settings-card', FULL: 'full-width', TITLE: 'settings-title', INFO_C: 'settings-card-info', ITEM: 'option-item', COL: 'option-item-col', FOOT: 'settings-footer', BTN: 'settings-btn', B_CLOSE: 'close', B_SAVE: 'save', B_RESET: 'reset', B_ACT: 'action', SW: 'switch', SL: 'slider', TARGETS: ['aliplayer', 'DPlayer', 'TCPlayer', 'xgplayer', 'Chimee', 'videojs', 'player'], LONG_PRESS: 400 }; const _ic = (d, c = '', v = '0 0 24 24') => ``; const ICONS = { BRIGHTNESS: _ic('M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z'), VOLUME_SIDE: _ic('M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z'), BIG_PLAY: _ic('M8 5v14l11-7z', 'icon-play'), BIG_PAUSE: _ic('M6 19h4V5H6v14zm8-14v14h4V5h-4z', 'icon-pause', '0 0 24 24" style="display: none;'), FORWARD: _ic('M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z', 'icon-progress', '0 0 24 24" style="display:none;'), REWIND: _ic('M11 18V6l-8.5 6L11 18zM20 6l-8.5 6 8.5 6V6z', 'icon-rewind', '0 0 24 24" style="display:none;'), VOLUME_ON: _ic('M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z', 'icon-volume-on'), VOLUME_OFF: _ic('M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z', 'icon-volume-off', '0 0 24 24" style="display: none;'), FS_ENTER: _ic('M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z', 'icon-fullscreen-enter'), FS_EXIT: _ic('M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z', 'icon-fullscreen-exit', '0 0 24 24" style="display: none;'), MORE: _ic('M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'), PIP: _ic('M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z'), PLAYBACK_RATE: _ic('M10 8v8l6-4-6-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z') }; const LOG = { INFO: 'ℹ️ INFO', MODULE: '📡 模块', CONFIG: '⚙️ 配置', ERROR: '❌ ERROR', WARN: '⚠️ WARN', TERM: '🛡️ TERMINATOR', PLAYER: '📺 播放器', LOCK: '🔒 播放器锁定', REVEAL: '✨ 播放器渲染', STREAM: '📶 流选择', SWITCH: '🔄 播放切换', HLS: '📽️ HLS', CORE: '👑 指挥中心', SLICE: '✂️ 广告过滤', URL: '🔗 URL解析', EXEC: '⚡️ 指令执行', IFRAME: '📦 沙箱', NEUT: '🔇 原生中和', SCAN: '🔍 扫描', SCAN_W: '🔍 扫描警告', HOOK: '🎣 播放器劫持', HOOK_F: '🎣 劫持失败', NAV: '🧭 导航', UI: '🎨 UI', INIT: '🛠️ 部署', LIFE: '♻️ LIFECYCLE', COMM: '📡 iFrame通信', FATAL: '📸 FATAL_SNAPSHOT', ATT: '🕵️‍♂️ 情报分析', SUCC: '🔊 接管成功', FAIL: '💀 接管失败', DECRYPT: '🗝️ 主动解混淆劫持' }; const SRC = { DOM: '页面元素扫描', ATTR: '页面元素扫描(data-*)', XHR: '网络拦截(XHR)', FETCH: '网络拦截(Fetch)', HOOK: (n) => `播放器接口劫持(${n})`, FRAME: 'iFrame 信使', ATOB: '主动解混淆劫持(atob)', JSON: '主动解混淆劫持(JSON)', SET: '主动解混淆劫持(attr)' }; const PLAYER_CSS = `:host{all:initial;position:fixed!important;background:transparent;overflow:visible;z-index:2147483646!important;display:flex;flex-direction:column;gap:0;padding:0;box-sizing:border-box;pointer-events:none;opacity:0;transition:opacity .3s ease-out;will-change:transform}:host(.dmz-visible){opacity:1}:host(.dmz-no-transition){transition:none!important}.${C.WRAP}{cursor:pointer;width:100%;flex-grow:1;display:flex;justify-content:center;align-items:center;background:#000;border-radius:20px;position:relative;overflow:hidden;box-shadow:0 12px 32px rgba(0,0,0,.25),0 2px 6px rgba(0,0,0,.1);max-height:0;flex-grow:0;-webkit-tap-highlight-color:transparent;pointer-events:auto;transition:max-height 1.2s cubic-bezier(.4,0,.2,1);will-change:max-height;touch-action:none}#${C.PLAYER}{width:100%;height:100%;object-fit:contain;background:#000;border-radius:20px;opacity:0;transition:opacity .3s ease;pointer-events:none}.${C.CLOSE}{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.4);color:#fff;width:26px;height:26px;border-radius:50%;display:flex;justify-content:center;align-items:center;font-size:20px;line-height:1;font-weight:700;cursor:pointer;z-index:30;border:1px solid rgba(255,255,255,.1);outline:none;-webkit-tap-highlight-color:transparent;opacity:0;visibility:hidden;pointer-events:none;transition:background .2s ease,transform .2s ease,opacity .3s ease,visibility .3s ease}.${C.WRAP}.dmz-controls-visible .${C.CLOSE}{opacity:1;visibility:visible;pointer-events:auto}.${C.CLOSE}:hover{background:rgba(230,50,90,.9);transform:scale(1.1)}.${C.CLOSE}:active{background-color:rgba(128,128,128,.5)}.dmz-control-button{display:flex;justify-content:center;align-items:center;width:30px;height:30px;background:transparent;border:none;padding:0;cursor:pointer;flex-shrink:0;color:#fff;outline:none;-webkit-tap-highlight-color:transparent;border-radius:50%;transition:background-color .2s ease}.dmz-control-button:active{background-color:rgba(128,128,128,.4)}.dmz-control-button svg{width:22px;height:22px;fill:currentColor;pointer-events:none}.dmz-big-play-button-container{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;pointer-events:none;z-index:22;opacity:0;transition:opacity .3s ease}.${C.WRAP}.dmz-controls-visible .dmz-big-play-button-container{opacity:1}.dmz-big-play-button{width:64px;height:64px;background:rgba(28,28,30,.5);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:2px solid rgba(255,255,255,.8);border-radius:50%;display:flex;justify-content:center;align-items:center;padding:0;cursor:pointer;pointer-events:auto;color:#fff;outline:none;-webkit-tap-highlight-color:transparent;transition:background .2s ease,transform .2s ease}.dmz-big-play-button:hover{transform:scale(1.1);background:rgba(28,28,30,.7)}.dmz-big-play-button:active{background:rgba(80,80,80,.6);transform:scale(1.05)}.dmz-big-play-button svg{width:32px;height:32px;fill:currentColor}.${C.DRAG}{width:calc(100% - 20px);margin:0 auto;height:18px;flex-shrink:0;cursor:move;display:flex;justify-content:center;align-items:center;position:relative;background:transparent;border-radius:9px;box-shadow:none;-webkit-tap-highlight-color:transparent;overflow:hidden;touch-action:none;pointer-events:auto}.${C.DRAG} .screw-effect{position:absolute;top:0;height:100%;width:calc(50% - 69px);background-image:none}.${C.DRAG} .indicator{width:80px;height:5px;background-color:#e0e0e0;border-radius:2.5px;box-shadow:0 0 3px rgba(0,0,0,.2);opacity:0;transition:opacity .3s ease,background-color .3s ease}.${C.DRAG}.dmz-indicator-visible .indicator{opacity:1}@media(prefers-color-scheme:dark){.${C.DRAG} .indicator{background-color:#424242;box-shadow:0 0 3px rgba(0,0,0,.5)}}.dmz-controls-bar{position:absolute;bottom:3px;left:10px;right:10px;width:auto;height:auto;background:rgba(28,28,30,.6);backdrop-filter:blur(15px);-webkit-backdrop-filter:blur(15px);border-radius:10px;display:flex;flex-direction:column;align-items:center;padding:2px 10px;box-sizing:border-box;opacity:1;visibility:visible;transition:opacity .3s ease,visibility .3s ease;z-index:25;pointer-events:auto;touch-action:none}.dmz-controls-bar.hidden{opacity:0;visibility:hidden;pointer-events:none}.dmz-time-display{color:#fff;font-size:13px;text-shadow:0 0 3px rgba(0,0,0,.8);margin:0 8px;font-family:sans-serif}.dmz-time-separator{color:#ccc;margin:0 4px;font-size:13px}.dmz-bottom-controls{width:100%;height:32px;display:flex;align-items:center}.dmz-spacer{flex-grow:1}.dmz-progress-container{width:100%;height:15px;display:flex;align-items:center}.dmz-progress-bar{flex-grow:1;height:100%;display:flex;align-items:center;padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent}.dmz-progress-rail{width:100%;height:4px;background:rgba(255,255,255,.3);position:relative;border-radius:2px}.dmz-progress-buffer{position:absolute;left:0;top:0;height:100%;width:0;background:rgba(255,255,255,.6);border-radius:2px;transition:width .1s linear}.dmz-progress-played{position:absolute;left:0;top:0;height:100%;width:0;background:#38b6ff;border-radius:2px}.dmz-progress-handle{position:absolute;top:50%;left:0;width:12px;height:12px;background:#fff;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 5px rgba(0,0,0,.5);pointer-events:none}.dmz-more-menu-container{position:relative}.dmz-more-menu{position:absolute;bottom:calc(100% + 5px);right:0;background:rgba(28,28,30,.8);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-radius:8px;width:180px;z-index:30;list-style:none;margin:0;padding:5px;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease;transform-origin:bottom right}.dmz-more-menu.visible{opacity:1;visibility:visible}.dmz-menu-item{display:flex;align-items:center;padding:8px 12px;color:#fff;font-size:14px;cursor:pointer;border-radius:6px}.dmz-menu-item:hover{background-color:rgba(255,255,255,.1)}.dmz-menu-item.disabled{color:#777;cursor:not-allowed;background:transparent!important}.dmz-menu-item svg{width:20px;height:20px;margin-right:10px;fill:currentColor;pointer-events:none}.dmz-menu-item .dmz-menu-item-label{flex-grow:1;pointer-events:none}.dmz-menu-item .dmz-menu-item-value{color:#aaa;pointer-events:none}.dmz-playback-rates{display:none}.dmz-playback-rates.visible{display:block}.dmz-playback-rates .dmz-menu-item.active{color:#38b6ff;font-weight:700}.dmz-gesture-indicator-wrapper{position:absolute;top:40px;left:50%;transform:translateX(-50%);background:rgba(28,28,30,.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff;padding:6px 12px;border-radius:18px;font-size:13px;font-family:sans-serif;display:flex;align-items:center;gap:6px;z-index:20;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.dmz-gesture-indicator-wrapper.visible{opacity:1;visibility:visible}.dmz-indicator-icon{display:flex;justify-content:center;align-items:center;width:24px;height:24px;color:#fff}.dmz-indicator-icon svg{width:100%;height:100%;fill:currentColor}.dmz-indicator-text{min-width:120px;text-align:center}.dmz-brightness-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#000;opacity:0;pointer-events:none;z-index:15;transition:opacity .1s ease}.dmz-side-indicator-container{position:absolute;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;align-items:center;gap:10px;opacity:0;transition:opacity .3s ease;pointer-events:none;z-index:20}.dmz-side-indicator-container.visible{opacity:1}.dmz-side-indicator-container.left{left:30px}.dmz-side-indicator-container.right{right:30px}.dmz-side-indicator-bar-wrapper{width:6px;height:120px;background-color:rgba(28,28,30,.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-radius:3px;display:flex;align-items:flex-end;overflow:hidden;border:1px solid rgba(255,255,255,.15);box-sizing:border-box}.dmz-side-indicator-fill{width:100%;height:0;background-color:#38b6ff;border-radius:3px}.dmz-side-indicator-fill.transition-active{transition:height .1s linear}.dmz-side-indicator-icon{width:24px;height:24px}.dmz-side-indicator-icon svg{width:100%;height:100%;fill:#fff;filter:drop-shadow(0 0 2px rgba(0,0,0,.5))}`; const UNIFIED_PANEL_CSS = `:host{all:initial;box-sizing:border-box;position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.5);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);--s-bg:rgba(28,28,34,.95);--s-border:rgba(56,182,255,.5);--s-shadow:rgba(0,0,0,.4);--s-text-main:#e5e5ea;--s-text-title:#fff;--s-text-label:#fff;--s-text-value:#ccc;--s-text-dim:#aaa;--s-card-bg:rgba(40,40,50,.8);--s-card-border:rgba(255,255,255,.1);--s-textarea-bg:#111;--s-input-bg:rgba(20,20,30,.8);--s-input-border:rgba(56,182,255,.5);--s-switch-bg:#58585a;--s-switch-handle:#fff;--s-switch-checked:#34c759;--s-info:#61dafb;--s-nav-btn-bg:rgba(255,255,255,0.08);--s-nav-btn-bg-active:var(--s-info)}:host(.visible){display:flex}@media(prefers-color-scheme:light){:host{--s-bg:rgba(252,252,252,.95);--s-border:rgba(0,122,255,.4);--s-shadow:rgba(0,0,0,.15);--s-text-main:#333;--s-text-title:#000;--s-text-label:#111;--s-text-value:#555;--s-text-dim:#666;--s-card-bg:rgba(255,255,255,.8);--s-card-border:rgba(0,0,0,.1);--s-textarea-bg:#f0f0f0;--s-input-bg:rgb(229,229,234);--s-input-border:rgba(0,0,0,.2);--s-switch-bg:#ccc;--s-info:#007aff;--s-nav-btn-bg:rgba(0,0,0,0.05)}}.dmz-unified-panel-wrapper{width:90%;max-width:550px;max-height:95%;background:var(--s-bg);border:1px solid var(--s-border);border-radius:12px;box-shadow:0 8px 32px var(--s-shadow);color:var(--s-text-main);display:flex;flex-direction:column;margin-bottom:env(safe-area-inset-bottom,20px)}.dmz-unified-panel-wrapper *{box-sizing:border-box}.panel-header{flex-shrink:0;padding:15px}.dmz-panel-nav{display:flex;justify-content:center;gap:10px;padding:0 15px 15px;border-bottom:1px solid var(--s-card-border)}.dmz-unified-panel-content{flex-grow:1;overflow-y:auto;overscroll-behavior:contain}.dmz-panel-pane{display:none;padding:15px;gap:15px;flex-direction:column}.dmz-panel-pane.active{display:flex}.dmz-unified-panel-footer{display:none;align-items:center;justify-content:flex-end;border-top:1px solid var(--s-card-border);gap:10px;flex-shrink:0;padding:15px;min-height:60px}.dmz-unified-panel-footer.active{display:flex}.title-bar{display:flex;justify-content:space-between;align-items:center}.title-bar h3{margin:0;font-size:18px;color:var(--s-text-title);text-shadow:0 0 5px rgba(56,182,255,.7);font-weight:600}.title-bar .close-btn{background:transparent;border:none;color:rgba(255,82,82,.7);font-size:26px;cursor:pointer;padding:0;font-weight:700;width:30px;height:30px;display:flex;align-items:center;justify-content:center;line-height:1}.title-bar .close-btn:hover{color:rgba(255,82,82,1)}.dmz-nav-btn{flex-grow:1;padding:5px;border-radius:6px;border:none;background:var(--s-nav-btn-bg);color:var(--s-text-dim);font-size:14px;font-weight:600;cursor:pointer;transition:background-color 0.2s,color 0.2s}.dmz-nav-btn.active{background:var(--s-nav-btn-bg-active);color:#fff}.copy-report-btn{display:block;width:100%;margin:0 0 10px;background:rgba(56,182,255,.15);border:1px solid rgba(56,182,255,.3);color:var(--s-info);padding:8px;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;text-align:center}.copy-report-btn:hover{background:rgba(56,182,255,.3)}.status-banner{padding:10px 15px;border-radius:8px;background:rgba(56,182,255,.15);border:1px solid rgba(56,182,255,.3);display:flex;align-items:center;gap:12px;font-size:16px;font-weight:600}.status-banner-icon{font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center}.cards-container{display:grid;grid-template-columns:1fr 1fr;gap:15px}.info-card{background:var(--s-card-bg);padding:12px 15px;border-radius:10px;border:1px solid var(--s-card-border);display:flex;flex-direction:column;gap:10px}.info-card.full-width{grid-column:1 / -1}.card-title{font-size:12px;color:var(--s-text-dim);font-weight:600;margin:0 0 5px;padding-bottom:5px;border-bottom:1px solid var(--s-card-border)}.card-content-item{display:flex;flex-direction:column;gap:8px;font-size:14px;align-items:flex-start}.label-wrapper{display:flex;justify-content:space-between;align-items:center;width:100%}.label{font-weight:600;color:var(--s-text-label)}.card-content-item .value{font-size:13px;color:var(--s-text-value);word-break:break-all}.card-content-item .value.success{color:#28a745}.card-content-item .value.warning{color:#ffc107}.card-content-item .value.error{color:#dc3545}.card-content-item .value ul{padding-left:18px;margin:5px 0 0}.card-content-item .value code{background:rgba(0,0,0,.05);padding:2px 5px;border-radius:4px;font-family:monospace;border:1px solid rgba(0,0,0,.05)}.copy-btn{background:rgba(0,122,255,.1);border:1px solid rgba(0,122,255,.2);color:#007aff;padding:3px 10px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:background .2s ease,color .2s ease}.copy-btn:hover{background:rgba(0,122,255,.2);color:#0056b3}.details-toggle{cursor:pointer;color:var(--s-info);margin-top:10px;text-align:center;font-weight:600;padding:8px;background:rgba(0,122,255,.08);border-radius:8px}textarea{width:100%;box-sizing:border-box;background:var(--s-textarea-bg);color:var(--s-text-value);border:1px solid var(--s-card-border);border-radius:6px;font-family:monospace;font-size:10px;resize:vertical;padding:8px;margin-top:8px}textarea[data-info="m3u8-raw"],textarea[data-info="m3u8-processed"]{min-height:50px}.dmz-log-container{width:100%;box-sizing:border-box;background:var(--s-textarea-bg);color:var(--s-text-value);border:1px solid var(--s-card-border);border-radius:6px;font-family:monospace;font-size:10px;resize:vertical;padding:8px;margin-top:8px;min-height:300px;max-height:300px;overflow-y:auto}.dmz-log-container pre{margin:0;white-space:pre-wrap;word-break:break-all}.log-entry{line-height:1.4}.log-collapsible{cursor:pointer;padding:4px 8px;background-color:rgba(56,182,255,.15);border-radius:4px;margin:4px 0;user-select:none;transition:background-color 0.2s}.log-collapsible:hover{background-color:rgba(56,182,255,.3)}.log-collapsed-content .log-entry{padding-left:15px;border-left:2px solid rgba(255,255,255,.2)}.${C.GRID}{display:flex;flex-direction:column;gap:15px}.${C.CARD}{background:var(--s-card-bg);padding:12px 15px;border-radius:10px;border:1px solid var(--s-card-border)}h3.settings-title{color:var(--s-info);margin:0 0 10px;padding-bottom:10px;border-bottom:1px solid var(--s-card-border);font-size:16px;font-weight:600}.${C.INFO_C}{background:rgba(56,182,255,.1);border:1px solid rgba(56,182,255,.25);padding:12px;border-radius:8px;color:var(--s-text-dim);margin-top:0;font-size:13px}.${C.INFO_C} b{font-weight:700;color:var(--s-text-main)}.${C.INFO_C} code{background:rgba(0,0,0,.05);padding:2px 5px;border-radius:4px;font-family:monospace;border:1px solid rgba(0,0,0,.08)}.${C.ITEM},.${C.COL}{display:flex;justify-content:space-between;align-items:center;padding:8px 0;color:var(--s-text-main);font-size:15px}.${C.COL}{flex-direction:column;align-items:flex-start;gap:8px}.${C.COL} label{font-weight:600}.${C.COL} input,.${C.COL} textarea{width:100%;box-sizing:border-box;background:var(--s-input-bg);border:1px solid var(--s-input-border);border-radius:6px;padding:10px;color:var(--s-text-main);font-family:sans-serif;font-size:14px}#site-blacklist-input{resize:vertical;min-height:120px;font-family:monospace}.${C.BTN}.${C.B_ACT}{background-color:rgba(0,122,255,.2);color:var(--s-info);margin-top:10px;width:100%;flex:none;border:1px solid rgba(0,122,255,.3)}.${C.BTN}.${C.B_ACT}:hover{background-color:rgba(0,122,255,.3)}.${C.BTN}{flex:0 1 auto;padding:8px 20px;border:none;border-radius:8px;cursor:pointer;color:#fff;font-size:15px;font-weight:600;box-shadow:0 2px 5px rgba(0,0,0,.1);transition:transform .1s ease,background-color .2s ease}.${C.BTN}:active{transform:scale(0.97)}.${C.BTN}.${C.B_CLOSE}{background-color:#555}.${C.BTN}.${C.B_SAVE}{background-color:#007aff}.${C.BTN}.${C.B_RESET}{background-color:#007aff}.${C.SW}{position:relative;display:inline-block;width:51px;height:31px}.${C.SW} input{opacity:0;width:0;height:0}.${C.SL}{position:absolute;cursor:pointer;inset:0;background-color:var(--s-switch-bg);transition:.4s;border-radius:31px}.${C.SL}:before{position:absolute;content:"";height:27px;width:27px;left:2px;bottom:2px;background-color:var(--s-switch-handle);transition:.4s;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2)}input:checked+.${C.SL}{background-color:var(--s-switch-checked)}input:checked+.${C.SL}:before{transform:translateX(20px)}`; const _ce = (t, o = {}, c = []) => { const e = document.createElement(t); Object.entries(o).forEach(([k, v]) => { if (k === 'style' && typeof v === 'object') Object.assign(e.style, v); else if (k in e) e[k] = v; else e.setAttribute(k, v); }); c.forEach(n => e.append(n)); return e; }; const _el = (e, t, f, o) => e.addEventListener(t, f, o); const ComponentHelper = { create: _ce, toggleIcon(s, h = []) { if (s) s.style.display = 'block'; h.forEach(i => { if (i) i.style.display = 'none'; }); } }; class EventManager { constructor() { this.listeners = []; } add(e, t, h, o) { e?.addEventListener(t, h, o); this.listeners.push({ e, t, h, o }); } removeAll() { for (const { e, t, h, o } of this.listeners) e?.removeEventListener(t, h, o); this.listeners = []; } } const Utils = { debounce(f, w) { let t; return function (...a) { clearTimeout(t); t = setTimeout(() => f.apply(this, a), w); }; }, createSwitchableModule(m) { return { ...m, enable(c) { if (!this.isActive && this.activate) { this.activate(c); c.actionLogger.log(`${m.name ?? ''} 已启用。`, LOG.MODULE); } }, disable(c) { if (this.isActive && this.deactivate) { this.deactivate(c); c?.actionLogger.log(`${m.name ?? ''} 已禁用。`, LOG.MODULE); } } }; }, wildcardToRegex(p) { return new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`); }, isM3U8(u) { return typeof u === 'string' && (u.toLowerCase().includes('.m3u8') || u.toLowerCase().includes('.m3u')); }, copyToClipboard(t, cb) { if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(t).then(cb).catch(e => console.error(`${SCRIPT_NAME}: Clipboard API failed`, e)); else { let a = _ce("textarea"); a.value = t; Object.assign(a.style, { position: 'fixed', top: '-9999px', left: '-9999px' }); document.body.appendChild(a); a.focus(); a.select(); try { if (document.execCommand('copy')) cb(); } catch (e) { console.error(`${SCRIPT_NAME}: execCommand failed`, e); } document.body.removeChild(a); } }, formatTime(s) { if (isNaN(s) || s < 0) return '00:00'; const d = new Date(s * 1000), h = d.getUTCHours(), m = d.getUTCMinutes().toString().padStart(2, '0'), sc = d.getUTCSeconds().toString().padStart(2, '0'); return h > 0 ? `${h}:${m}:${sc}` : `${m}:${sc}`; }, toggleVisibility(e, f) { if (!e) return false; e.classList.toggle('visible', f); return e.classList.contains('visible'); }, createCopyButtonWithFeedback(t, b = '复制') { const n = _ce('button', { className: 'copy-btn', title: '复制', textContent: b }); _el(n, 'click', e => { e.stopPropagation(); this.copyToClipboard(t, () => { n.textContent = '✔ 已复制!'; setTimeout(() => { n.textContent = b; }, 1500); }); }); return n; }, showButtonFeedback(b, { successText, originalContent, duration = 1500, successBgColor = '' }) { const w = b.style.width, bg = b.style.backgroundColor, c = originalContent ?? b.innerHTML, pw = b.getBoundingClientRect().width; b.style.width = `${pw}px`; b.textContent = successText; if (successBgColor) b.style.backgroundColor = successBgColor; setTimeout(() => { b.innerHTML = c; b.style.width = w; b.style.backgroundColor = bg; }, duration); }, getVideoFormat(u) { if (typeof u !== 'string') return { name: '未知', type: '未知' }; if (this.isM3U8(u)) return { name: "M3U8", type: "流式传输" }; const c = u.split('?')[0].toLowerCase(), fs = [{ ext: '.mp4', name: 'MP4' }, { ext: '.mkv', name: 'MKV' }, { ext: '.webm', name: 'WebM' }, { ext: '.flv', name: 'FLV' }], f = fs.find(x => c.endsWith(x.ext)); if (f) return { name: f.name, type: "常规文件" }; try { const o = new URL(u); for (const [k, v] of o.searchParams.entries()) { const lv = v.toLowerCase(), pf = fs.find(x => lv.endsWith(x.ext)); if (pf) return { name: pf.name, type: "常规文件" }; if ((k.includes('type') || k.includes('format')) && fs.some(x => lv.includes(x.ext.substring(1)))) return { name: fs.find(x => lv.includes(x.ext.substring(1))).name, type: "常规文件" }; } } catch (e) { } return { name: "未知", type: "常规文件" }; }, isHighNoisePage() { const p = window.location.pathname.toLowerCase() + window.location.search.toLowerCase(); if (/(play|piay|video(?!s?(\/)?$)|watch|view|episode|embed|\/(anime|drama|movies?|chinese-subtitles|mhkh|zy|hd-uncensored|short|film|dm|tv(?!s?\/+[a-z]))\/(?!$|\?|index|home_list|[^?0-9]+\/$)|archives|\/dy\/|\/dj\/|\/dsj\/|\/p\/|huishou|\/mjbo\/|\/v\/(?!$|.*\.html)|\/[a-z0-9-_]{9,}\/\d{5,})/i.test(p)) return false; if (/^\/-\d+/.test(p)) return false; return /(^|\/|\?|&|-)(s|search|find|query|results?|so|list|cate|categor(y|ies)|tags?|t|topic|genres?|channels?|feed|home|index|default|forum|node|board|uid|user|profile|vodtype|vodshow|type|class|sort|model|star|detail|new|hot|like)($|\/|\.|_|\?|&|=|-)/i.test(p) || /\/(drama|movies?|anime)\/?(\?|$)/i.test(p) || /(^|\/)(pp|pg|page)\d+($|\/)/i.test(p) || /^(\/|\/index\.(html|php|jsp|asp|aspx))(\?|$)/i.test(p) || /\/[a-z0-9-_]+\/\d+($|\/|\.)/i.test(p) || /^\/[a-z0-9-_]+(\/|$|\?)/i.test(p) || /(^|\/)(t|tag|tags)\//i.test(p); }, stopEvent(e) { e.stopPropagation(); if (e.cancelable) e.preventDefault(); } }; const IframeTerminator = { observer: null, keywords: ['/acs/phone/', 'googleads', 'googlesyndication', 'doubleclick.net', '/ad/'], processedIframes: new WeakSet(), actionLogger: null, init(c) { this.actionLogger = c.actionLogger; }, terminate(i) { if (this.processedIframes.has(i)) return false; try { if (i.src && this.keywords.some(k => i.src.includes(k))) { this.actionLogger?.log(`IframeTerminator: 禁用干扰性 iFrame -> ${i.src}`, LOG.TERM); this.processedIframes.add(i); i.src = 'about:blank'; i.style.setProperty('visibility', 'hidden', 'important'); i.style.setProperty('border', 'none', 'important'); return true; } } catch (e) { } return false; }, scanAndTerminate(n) { if (n?.nodeType !== Node.ELEMENT_NODE) return; (n.matches('iframe') ? [n] : n.querySelectorAll('iframe')).forEach(i => this.terminate(i)); }, start() { if (this.observer || !IS_TOP_FRAME) return; this.observer = new MutationObserver(ms => { for (const m of ms) for (const n of m.addedNodes) this.scanAndTerminate(n); }); this.observer.observe(document.documentElement, { childList: true, subtree: true }); document.readyState === 'loading' ? _el(window, 'DOMContentLoaded', () => this.scanAndTerminate(document.documentElement), { once: true }) : this.scanAndTerminate(document.documentElement); }, stop() { this.observer?.disconnect(); this.observer = null; } }; class ActionLogger { constructor() { this.logs = []; this.maxEntries = 150; this.diagnosticsTool = null; } setContext(c) { this.diagnosticsTool = c.diagnosticsTool; this.context = c; } log(m, t = LOG.INFO) { if (this.logs.length >= this.maxEntries) this.logs.shift(); const e = { type: t, message: m, frame: IS_TOP_FRAME ? 'Top' : 'iFrame' }; this.logs.push(e); this.diagnosticsTool?.logEvent(e); if (!IS_TOP_FRAME) { const fs = [LOG.NEUT, LOG.SCAN, LOG.SCAN_W, LOG.IFRAME, LOG.EXEC, LOG.CORE, LOG.ATT, LOG.FAIL, LOG.WARN, LOG.ERROR]; if (fs.includes(t)) { this.context?.frameCommunicator?.postToTopFrame({ type: MSG.LOG, logEntry: { type: t, message: m } }); } } } getLogs() { return this.logs; } } class DiagnosticsTool { constructor(c) { this.context = c; this.eventTimeline = []; this.fatalNetworkErrors = []; this.maxLogEntries = 300; this.startTime = new Date(); this.takeoverEvidence = { sources: [], url: null }; this.lastProcessedM3u8 = null; this.playbackHealth = {}; this.slicingReport = {}; this.logSequence = 0; this.resetPlaybackHealth(); } captureTakeoverEvidence(c) { this.takeoverEvidence = { sources: Array.from(c.sources), url: c.url, iframeSource: c.iframeSource || null, iframeOrigin: c.iframeOrigin || null }; } captureFatalSnapshot(ec) { const { settingsManager, playerManager } = this.context; this.logEvent({ type: LOG.FATAL, message: `捕获到致命错误快照`, details: JSON.stringify({ error: ec, config: { ...settingsManager.config }, playerState: { isActive: playerManager.isPlayerActiveOrInitializing, videoType: playerManager.currentVideoType, videoUrl: playerManager.currentVideoUrl, videoFormat: playerManager.currentVideoFormat, hlsState: playerManager.hlsInstance?.state }, health: { ...this.playbackHealth } }, null, 2) }); } logEvent(e) { if (this.eventTimeline.length >= this.maxLogEntries) this.eventTimeline.shift(); const n = new Date(); e.time = n; e.relativeTime = `+${((n - this.startTime) / 1000).toFixed(3)}s`; e.sequence = this.logSequence++; this.eventTimeline.push(e); if (e.type === 'FATAL_NET_ERROR') this.fatalNetworkErrors.push(e); } init() { this.context.actionLogger.log(`脚本 v${SCRIPT_VERSION} 开始执行 (document-start)`, LOG.LIFE); } resetPlaybackHealth() { this.playbackHealth = { manifest: { status: 'pending', code: null }, key: { status: 'pending', code: null }, media: { status: 'pending', reason: null }, segments: { status: 'pending', errorCount: 0, consecutiveErrors: 0 } }; } resetSlicingReport() { this.slicingReport = { totalGroups: 0, slicedGroups: 0, slicedSegments: 0, slicedDuration: 0, slicedTimeRanges: [], activatedEngines: new Set(), foundFeatures: new Map([['URL_PATTERN', new Set()], ['BEHAVIOR_MODEL', new Set()]]) }; } analyzeUrlPatterns(c, b) { if (!c || typeof c !== 'string' || !b) return null; const ls = c.split('\n'), segs = []; let cd = 0; for (const l of ls) { const tl = l.trim(); if (tl.startsWith('#EXTINF:')) cd = parseFloat(tl.split(':')[1]); else if ((tl.endsWith('.ts') || tl.includes('.jpeg')) && !tl.startsWith('#')) { try { const u = new URL(tl, b), p = u.pathname, ls = p.lastIndexOf('/'); segs.push({ url: u.href, duration: cd, path: (ls > -1) ? p.substring(0, ls + 1) : '/', filename: p.split('/').pop() || '' }); } catch (e) { } } } if (segs.length < 10) return null; const ps = new Map(); segs.forEach(s => { ps.set(s.path, (ps.get(s.path) || 0) + 1); }); if (ps.size <= 1) { this.context.actionLogger.log(`所有分片路径统一,无法通过路径独特性分析。`, LOG.SLICE); return null; } const sp = Array.from(ps.entries()).sort((a, b) => b[1] - a[1]), [mp, mpc] = sp[0], sugs = new Set(); for (let i = 1; i < sp.length; i++) { const [sup, supc] = sp[i]; if (supc < mpc / 2) { const ap = sup.split('/').filter(Boolean), bp = mp.split('/').filter(Boolean); for (let j = 0; j < Math.min(ap.length, bp.length); j++) { if (ap[j] !== bp[j]) { sugs.add(ap[j]); this.context.actionLogger.log(`基于路径独特性发现高嫌疑关键词: ${ap[j]}`, LOG.SLICE); break; } } if (sugs.size === 0 && ap.length !== bp.length) { const lg = ap.length > bp.length ? ap : bp, sh = ap.length > bp.length ? bp : ap; if (lg.join('/').startsWith(sh.join('/'))) { const dp = lg[sh.length]; if (dp) { sugs.add(dp); this.context.actionLogger.log(`基于路径层级差异发现高嫌疑关键词: ${dp}`, LOG.SLICE); } } } } } if (sugs.size > 0) { this.slicingReport.activatedEngines.add('URL_PATTERN'); sugs.forEach(s => this.slicingReport.foundFeatures.get('URL_PATTERN').add(s)); return Array.from(sugs); } this.context.actionLogger.log(`路径分析未得出结论,回退至基于时长的聚类分析。`, LOG.SLICE); const dcs = new Map(); segs.forEach(s => { const k = s.duration.toFixed(2); if (!dcs.has(k)) dcs.set(k, []); dcs.get(k).push(s); }); if (dcs.size <= 1) return null; const sc = Array.from(dcs.values()).sort((a, b) => b.length - a.length), bc = sc[0]; sc.forEach(c => { if (c === bc) return; if (c.length <= 5 && c.every(s => s.duration < 20)) { const ds = c.map(s => s.duration), m = ds.reduce((a, v) => a + v, 0) / ds.length, v = ds.map(d => (d - m) ** 2).reduce((a, v) => a + v, 0) / ds.length; if (v < 0.01) { const as = c[0], bs = bc[0]; if (as.path !== bs.path) { const ap = as.path.split('/').filter(Boolean), bp = bs.path.split('/').filter(Boolean); for (let i = 0; i < Math.min(ap.length, bp.length); i++) { if (ap[i] !== bp[i]) { sugs.add(ap[i]); this.context.actionLogger.log(`[广告过滤-回退] 通过对比发现潜在广告关键词: ${ap[i]}`, LOG.SLICE); return; } } } } } }); if (sugs.size > 0) { this.slicingReport.activatedEngines.add('URL_PATTERN'); sugs.forEach(s => this.slicingReport.foundFeatures.get('URL_PATTERN').add(s)); } return Array.from(sugs); } generateDeveloperReport() { const nl = '\n'; let rh = `大魔王视频助手 开发者日志 (版本: ${SCRIPT_VERSION})${nl}页面URL: ${window.location.href}${nl}`; if (this.fatalNetworkErrors.length > 0) { rh += `--- 深度网络故障分析 ---${nl}`; this.fatalNetworkErrors.forEach((e, i) => { rh += `[错误 ${i + 1}] @ ${e.relativeTime}: ${e.message}${nl}`; }); } rh += `--- 统一事件时间轴 ---${nl}`; const sl = [...this.eventTimeline].sort((a, b) => b.time - a.time || b.sequence - a.sequence), CP = /(嵌入式扫描器发现广告链接,已跳过|\[路径特征分析\] URL判定为广告)/; let ho = `
${rh.replace(//g, ">").replace(/\n/g, '
')}
`, cl = [], ic = false; const fc = () => { if (cl.length > 0) { ho += `
[+] 折叠了 ${cl.length} 条 "广告扫描" 噪音日志... (点击展开)
`; cl = []; } }; sl.forEach(l => { const ft = l.type.replace(' ', ' [') + ']', lm = `[${l.relativeTime}] ${ft} ${l.message}`, em = lm.replace(//g, ">"), clh = `
${em}
`; if (l.type === LOG.SCAN_W && CP.test(l.message)) { ic = true; cl.push(clh); } else { if (ic) { fc(); ic = false; } if (l.message.length > 500 && (l.message.includes('base64,') || l.message.includes('M3U8处理完成'))) ho += `
[+] 折叠了过长的硬编码数据... (点击展开)
`; else ho += clh; } }); fc(); return ho; } } class Draggable { constructor(c, e, h, pm) { this.context = c; this.element = e; this.handles = h; this.playerManager = pm; this.isDragging = false; this.dragStartPos = { x: 0, y: 0 }; this.elementStartPos = { x: 0, y: 0 }; this.boundHandleDragStart = this.handleDragStart.bind(this); this.boundHandleDragMove = this.handleDragMove.bind(this); this.boundHandleDragEnd = this.handleDragEnd.bind(this); this.addListeners(); } addListeners() { this.handles.forEach(h => _el(h, 'pointerdown', this.boundHandleDragStart, { capture: true })); } destroy() { this.removeDragListeners(); this.handles.forEach(h => h.removeEventListener('pointerdown', this.boundHandleDragStart, { capture: true })); this.element = null; } removeDragListeners() { document.removeEventListener('pointermove', this.boundHandleDragMove, { capture: true }); document.removeEventListener('pointerup', this.boundHandleDragEnd, { capture: true }); document.removeEventListener('pointercancel', this.boundHandleDragEnd, { capture: true }); } handleDragStart(e) { if (!this.element || e.button !== 0) return; Utils.stopEvent(e); this.isDragging = true; this.playerManager?.showIndicatorPermanently(true); this.element.style.transition = 'none'; const r = this.element.getBoundingClientRect(); this.element.style.left = `0px`; this.element.style.top = `0px`; this.element.style.transform = `translate3d(${r.left}px, ${r.top}px, 0)`; this.elementStartPos = { x: r.left, y: r.top }; this.dragStartPos = { x: e.clientX, y: e.clientY }; _el(document, 'pointermove', this.boundHandleDragMove, { capture: true }); _el(document, 'pointerup', this.boundHandleDragEnd, { capture: true }); _el(document, 'pointercancel', this.boundHandleDragEnd, { capture: true }); } handleDragMove(e) { if (!this.isDragging || !this.element) return; Utils.stopEvent(e); let nx = this.elementStartPos.x + (e.clientX - this.dragStartPos.x), ny = this.elementStartPos.y + (e.clientY - this.dragStartPos.y); nx = Math.max(0, Math.min(nx, window.innerWidth - this.element.offsetWidth)); ny = Math.max(0, Math.min(ny, window.innerHeight - this.element.offsetHeight)); this.element.style.transform = `translate3d(${nx}px, ${ny}px, 0)`; } handleDragEnd(e) { if (!this.isDragging || !this.element) return; e.stopPropagation(); this.isDragging = false; this.removeDragListeners(); this.playerManager?.resetIndicatorTimeout(); this.element.style.transition = ''; const m = new DOMMatrix(window.getComputedStyle(this.element).transform), fp = { top: `${m.m42}px`, left: `${m.m41}px` }; this.element.style.left = fp.left; this.element.style.top = fp.top; this.element.style.transform = ''; const iv = this.playerManager.videoElement.videoHeight > this.playerManager.videoElement.videoWidth, vok = iv ? 'verticalVideo' : 'horizontalVideo', il = screen.orientation.type.includes('landscape'), pok = il ? 'landscape' : 'portrait'; this.context.settingsManager.savePlayerPosition(window.location.hostname, `${pok}_${vok}`, fp); } } class CustomControlsHandler { constructor(c, ct, v) { this.context = c; this.container = ct; this.video = v; this.eventManager = new EventManager(); this.hideTimeout = null; this.isSeeking = false; this.lastKnownVolumeBeforeMute = 1.0; this.boundHandleVolumeChange = this.handleVolumeChange.bind(this); this.boundHandleTimeUpdate = this.handleTimeUpdate.bind(this); this.boundHandleDurationChange = this.handleDurationChange.bind(this); this.boundHandleProgress = this.handleProgress.bind(this); this.boundOnPlay = this.onPlay.bind(this); this.boundOnPause = this.onPause.bind(this); this.boundHandleTap = this.handleTap.bind(this); this.boundResetHideTimeout = this.resetHideTimeout.bind(this); this.boundHideControls = this.hideControls.bind(this); this.controlActions = { '.dmz-big-play-button': this.togglePlay.bind(this), '.dmz-fullscreen-btn': this.toggleFullscreen.bind(this), '.dmz-mute-btn': this.toggleMute.bind(this), '.dmz-more-btn': this.toggleMoreMenu.bind(this), '.dmz-pip-btn': this.togglePip.bind(this), '.dmz-playback-rate-btn': this.showPlaybackRates.bind(this) }; } init() { this.controlsBar = this.container.querySelector('.dmz-controls-bar'); this.bigPlayButton = this.container.querySelector('.dmz-big-play-button'); this.currentTimeEl = this.container.querySelector('.dmz-current-time'); this.totalTimeEl = this.container.querySelector('.dmz-total-time'); this.progressBar = this.container.querySelector('.dmz-progress-bar'); this.playedBar = this.container.querySelector('.dmz-progress-played'); this.bufferBar = this.container.querySelector('.dmz-progress-buffer'); this.handle = this.container.querySelector('.dmz-progress-handle'); this.fullscreenEnterIcon = this.container.querySelector('.dmz-fullscreen-btn .icon-fullscreen-enter'); this.fullscreenExitIcon = this.container.querySelector('.dmz-fullscreen-btn .icon-fullscreen-exit'); this.volumeOnIcon = this.container.querySelector('.dmz-mute-btn .icon-volume-on'); this.volumeOffIcon = this.container.querySelector('.dmz-mute-btn .icon-volume-off'); this.moreMenu = this.container.querySelector('.dmz-more-menu'); this.pipBtn = this.container.querySelector('.dmz-pip-btn'); this.playbackRateBtn = this.container.querySelector('.dmz-playback-rate-btn'); this.playbackRateValue = this.container.querySelector('.dmz-playback-rate-btn .dmz-menu-item-value'); this.playbackRatesContainer = this.container.querySelector('.dmz-playback-rates'); this.lastKnownVolumeBeforeMute = this.context.settingsManager.config.lastVolume; this.addEventListeners(); this.updatePlayButton(); this.updateFullscreenButton(); this.updateMuteButton(); if (!document.pictureInPictureEnabled && this.pipBtn) this.pipBtn.style.display = 'none'; } addEventListeners() { this.eventManager.add(this.video, 'play', this.boundOnPlay); this.eventManager.add(this.video, 'pause', this.boundOnPause); this.eventManager.add(this.video, 'volumechange', this.boundHandleVolumeChange); this.eventManager.add(this.video, 'timeupdate', this.boundHandleTimeUpdate); this.eventManager.add(this.video, 'durationchange', this.boundHandleDurationChange); this.eventManager.add(this.video, 'progress', this.boundHandleProgress); this.eventManager.add(this.container, 'mousemove', this.boundResetHideTimeout); const s = (e) => e.stopPropagation(); if (this.controlsBar) this.eventManager.add(this.controlsBar, 'pointerdown', s, { capture: true }); if (this.controlsBar) this.eventManager.add(this.controlsBar, 'click', this.boundHandleTap, { capture: true }); if (this.bigPlayButton) this.eventManager.add(this.bigPlayButton, 'click', this.boundHandleTap, { capture: true }); } destroy() { this.eventManager.removeAll(); clearTimeout(this.hideTimeout); } handleTap(e) { e.stopPropagation(); this.handleTapOnControl(e.target); } onPlay() { this.updatePlayButton(); const { playerManager, diagnosticsTool, coreLogic } = this.context; if (playerManager.currentVideoType === 'normal') diagnosticsTool.playbackHealth.media.status = 'success'; else if (diagnosticsTool.playbackHealth.media.status === 'pending') diagnosticsTool.playbackHealth.media.status = 'success'; const cu = playerManager.currentVideoUrl; if (!cu) return; const guc = (u) => { try { return new URL(u).pathname; } catch (e) { return u; } }, puc = guc(cu); coreLogic.findAllVideosAndAudioInPage().forEach(m => { if (m.id === C.PLAYER || !m.dataset.dmzNeutralized) return; const os = m.dataset.dmzOriginalSrc; if (os && guc(os) === puc) { coreLogic.neutralizedMedia.add(m); coreLogic.startPersistentEnforcer(); } }); } onPause() { this.updatePlayButton(); } handleTapOnControl(t) { if (!t) return false; const ak = Object.keys(this.controlActions).find(s => t.closest(s)); if (ak) { this.controlActions[ak](); return true; } const rb = t.closest('[data-rate]'); if (rb) { this.setPlaybackRate(parseFloat(rb.dataset.rate)); return true; } return false; } toggleMoreMenu() { const v = Utils.toggleVisibility(this.moreMenu); if (v) { Utils.toggleVisibility(this.playbackRatesContainer, false); if (this.pipBtn) this.pipBtn.style.display = document.pictureInPictureEnabled ? 'flex' : 'none'; if (this.playbackRateBtn) this.playbackRateBtn.style.display = 'flex'; } } hideMoreMenu() { Utils.toggleVisibility(this.moreMenu, false); } togglePip() { this.hideMoreMenu(); if (!document.pictureInPictureElement) this.video.requestPictureInPicture().catch(e => this.context.actionLogger.log(`请求画中画失败: ${e.message}`, LOG.ERROR)); else document.exitPictureInPicture(); } showPlaybackRates() { if (!this.playbackRatesContainer || !this.playbackRateBtn) return; if (this.pipBtn) this.pipBtn.style.display = 'none'; if (this.playbackRateBtn) this.playbackRateBtn.style.display = 'none'; Utils.toggleVisibility(this.playbackRatesContainer, true); } setPlaybackRate(r) { this.video.playbackRate = r; if (this.playbackRateValue) this.playbackRateValue.textContent = r === 1.0 ? '正常' : `${r}x`; if (this.playbackRatesContainer) { this.playbackRatesContainer.querySelector('.active')?.classList.remove('active'); this.playbackRatesContainer.querySelector(`[data-rate="${r}"]`)?.classList.add('active'); } this.hideMoreMenu(); } toggleControlsVisibility() { clearTimeout(this.hideTimeout); this.hideMoreMenu(); this.controlsBar?.classList.contains('hidden') ? this.resetHideTimeout() : this.hideControls(); } updateSeekUI(cx) { if (!this.progressBar || !this.video?.duration) return; const r = this.progressBar.getBoundingClientRect(); let p = (cx - r.left) / r.width; p = Math.max(0, Math.min(1, p)); const nt = p * this.video.duration; if (isNaN(nt)) return; const pc = p * 100; if (this.playedBar) this.playedBar.style.width = `${pc}%`; if (this.handle) this.handle.style.left = `${pc}%`; if (this.currentTimeEl) this.currentTimeEl.textContent = Utils.formatTime(nt); return nt; } togglePlay() { if (this.video) this.video.paused ? this.video.play() : this.video.pause(); } toggleFullscreen() { this.context.playerManager.fullscreenHandler?.toggleState(); } toggleMute() { if (!this.video) return; if (this.video.muted || this.video.volume === 0) { this.video.volume = this.lastKnownVolumeBeforeMute > 0.05 ? this.lastKnownVolumeBeforeMute : 0.5; this.video.muted = false; } else this.video.muted = true; this.context.settingsManager.save({ isMuted: this.video.muted }, false); } handleVolumeChange() { this.updateMuteButton(); const em = this.video.muted || this.video.volume === 0; if (em) this.context.settingsManager.save({ isMuted: true }, false); else { this.lastKnownVolumeBeforeMute = this.video.volume; this.context.settingsManager.save({ lastVolume: this.video.volume, isMuted: false }, false); } } updatePlayButton() { const b = this.container.querySelector('.dmz-big-play-button'), c = this.container.querySelector('.dmz-big-play-button-container'); if (!b) return; if (this.video.paused) { b.innerHTML = ICONS.BIG_PLAY; if (c) c.style.opacity = '1'; } else { b.innerHTML = ICONS.BIG_PAUSE.replace(/^]+>/, ''); if (c) c.style.opacity = ''; } } updateFullscreenButton() { const f = !!document.fullscreenElement; ComponentHelper.toggleIcon(f ? this.fullscreenExitIcon : this.fullscreenEnterIcon, [f ? this.fullscreenEnterIcon : this.fullscreenExitIcon]); } updateMuteButton() { const m = this.video.muted || this.video.volume === 0; ComponentHelper.toggleIcon(m ? this.volumeOffIcon : this.volumeOnIcon, [m ? this.volumeOnIcon : this.volumeOffIcon]); } handleTimeUpdate() { if (this.isSeeking || !isFinite(this.video.duration)) return; requestAnimationFrame(() => { if (this.video && this.playedBar && this.handle && this.currentTimeEl) { const p = (this.video.currentTime / this.video.duration) * 100; this.playedBar.style.width = `${p}%`; this.handle.style.left = `${p}%`; this.currentTimeEl.textContent = Utils.formatTime(this.video.currentTime); } }); } handleDurationChange() { if (this.totalTimeEl) this.totalTimeEl.textContent = Utils.formatTime(this.video.duration); } handleProgress() { if (this.video.duration > 0 && this.video.buffered.length > 0 && this.bufferBar) this.bufferBar.style.width = `${(this.video.buffered.end(this.video.buffered.length - 1) / this.video.duration) * 100}%`; } showControls() { this.controlsBar?.classList.remove('hidden'); this.container.classList.add('dmz-controls-visible'); } hideControls() { this.controlsBar?.classList.add('hidden'); this.container.classList.remove('dmz-controls-visible'); this.hideMoreMenu(); } resetHideTimeout() { this.showControls(); clearTimeout(this.hideTimeout); this.hideTimeout = setTimeout(this.boundHideControls, 1500); } } class FullscreenGestureHandler { constructor(c, ct, v) { this.context = c; this.container = ct; this.video = v; this.isEnabled = false; this.indicator = ct.querySelector('.dmz-gesture-indicator-wrapper'); this.indicatorIconContainer = this.indicator?.querySelector('.dmz-indicator-icon'); this.indicatorText = this.indicator?.querySelector('.dmz-indicator-text'); this.brightnessOverlay = ct.querySelector('.dmz-brightness-overlay'); this.brightnessIndicator = ct.querySelector('.dmz-side-indicator-container.left'); this.brightnessFill = this.brightnessIndicator?.querySelector('.dmz-side-indicator-fill'); this.volumeIndicator = ct.querySelector('.dmz-side-indicator-container.right'); this.volumeFill = this.volumeIndicator?.querySelector('.dmz-side-indicator-fill'); this.isSwiping = false; this.swipeType = null; this.startX = 0; this.startY = 0; this.startCurrentTime = 0; this.startVolume = 0; this.startBrightness = 0; this.hideIndicatorTimeout = null; this.longPressTimer = null; this.isFastForwarding = false; this.originalPlaybackRate = 1.0; this.lastTapTime = 0; } deactivate() { clearTimeout(this.longPressTimer); clearTimeout(this.hideIndicatorTimeout); } enable() { this.isEnabled = true; } disable() { this.isEnabled = false; } onPointerDown(e) { this.isSwiping = false; this.swipeType = null; this.startX = e.clientX; this.startY = e.clientY; this.startCurrentTime = this.video.currentTime; this.startVolume = this.video.volume; this.startBrightness = this.brightnessOverlay ? (parseFloat(this.brightnessOverlay.style.opacity) || 0) : 0; clearTimeout(this.longPressTimer); this.longPressTimer = setTimeout(() => { if (!this.isSwiping) this.activateFastForward(); }, C.LONG_PRESS); } onPointerMove(e) { const dx = e.clientX - this.startX, dy = e.clientY - this.startY; if (!this.isSwiping && (Math.abs(dx) > 10 || Math.abs(dy) > 10)) { clearTimeout(this.longPressTimer); this.isSwiping = true; this.volumeFill?.classList.remove('transition-active'); this.brightnessFill?.classList.remove('transition-active'); } if (this.isSwiping) { if (!this.swipeType) { const adx = Math.abs(dx), ady = Math.abs(dy); if (adx > ady * 2.0) this.swipeType = 'progress'; else if (ady > adx * 2.0) { const pr = this.container.getBoundingClientRect(); this.swipeType = (this.startX < pr.left + pr.width / 2) ? 'brightness' : 'volume'; } } const ya = (dy * 3.0) / window.innerHeight; switch (this.swipeType) { case 'progress': { if (!isFinite(this.video.duration)) break; const nt = this.startCurrentTime + (dx / window.innerWidth) * (this.video.duration * 0.15); this.video.currentTime = Math.max(0, Math.min(nt, this.video.duration)); this.context.playerManager.customControlsHandler?.handleTimeUpdate(); this.showIndicator(dx > 0 ? 'progress' : 'rewind', `${Utils.formatTime(this.video.currentTime)} (${Math.round((this.video.currentTime / this.video.duration) * 100)}%)`); break; } case 'volume': { const nv = Math.max(0, Math.min(this.startVolume - ya, 1)); if (nv > 0 && this.video.muted) this.video.muted = false; this.video.volume = nv; this.showIndicator('volume', null, this.video.volume * 100); break; } case 'brightness': { if (!this.brightnessOverlay) break; const nb = this.startBrightness + ya; this.brightnessOverlay.style.opacity = Math.max(0, Math.min(nb, 0.8)); this.showIndicator('brightness', null, (1 - parseFloat(this.brightnessOverlay.style.opacity) / 0.8) * 100); break; } } } } onPointerUp(e) { this.volumeFill?.classList.add('transition-active'); this.brightnessFill?.classList.add('transition-active'); clearTimeout(this.longPressTimer); if (this.isFastForwarding) this.deactivateFastForward(); else if (this.isSwiping) this.hideIndicator(); else { const { customControlsHandler: cch } = this.context.playerManager; if (e.target.closest('.dmz-big-play-button')) cch?.togglePlay(); else { const n = Date.now(); if (n - this.lastTapTime < 300) { cch?.toggleFullscreen(); this.lastTapTime = 0; } else { this.lastTapTime = n; if (this.video.dataset.dmzStrategicMute === 'true') { this.video.muted = false; this.context.actionLogger.log("首次交互,已自动恢复声音。", LOG.PLAYER); cch?.updateMuteButton(); delete this.video.dataset.dmzStrategicMute; } cch?.toggleControlsVisibility(); } } } this.isSwiping = false; this.swipeType = null; } activateFastForward() { if (!this.video || this.isFastForwarding) return; const lpr = this.context.settingsManager.config.longPressRate || 2.0; this.isFastForwarding = true; this.originalPlaybackRate = this.video.playbackRate; this.video.playbackRate = lpr; this.showIndicator('progress', `${lpr}x 倍速播放`); this.context.playerManager.customControlsHandler?.hideControls(); this.context.playerManager.showIndicatorPermanently(false); this.context.actionLogger.log(`长按倍速已激活`, LOG.PLAYER); } deactivateFastForward() { if (!this.video || !this.isFastForwarding) return; this.isFastForwarding = false; this.video.playbackRate = this.originalPlaybackRate; this._hideAllIndicators(); this.context.actionLogger.log(`长按倍速已结束,恢复原速`, LOG.PLAYER); } _hideAllIndicators() { this.indicator?.classList.remove('visible'); this.brightnessIndicator?.classList.remove('visible'); this.volumeIndicator?.classList.remove('visible'); } _updateSideIndicator(i, f, v) { if (!i || !f) return; i.classList.add('visible'); f.style.height = `${v}%`; } showIndicator(t, txt, v) { clearTimeout(this.hideIndicatorTimeout); this._hideAllIndicators(); if (t === 'brightness') this._updateSideIndicator(this.brightnessIndicator, this.brightnessFill, v); else if (t === 'volume') this._updateSideIndicator(this.volumeIndicator, this.volumeFill, v); else if (this.indicator && this.indicatorIconContainer && this.indicatorText) { Array.from(this.indicatorIconContainer?.querySelectorAll('svg') || []).forEach(s => (s.style.display = 'none')); const i = this.indicatorIconContainer.querySelector(t === 'rewind' ? '.icon-rewind' : `.icon-${t}`); if (i) i.style.display = 'block'; this.indicatorText.textContent = txt; this.indicator.classList.add('visible'); } } hideIndicator() { this.hideIndicatorTimeout = setTimeout(() => this._hideAllIndicators(), 800); } } class FullscreenHandler { constructor(c, ct) { this.context = c; this.containerElement = ct; this.eventManager = new EventManager(); this.onFullscreenChange = this.onFullscreenChange.bind(this); this.eventManager.add(document, 'fullscreenchange', this.onFullscreenChange); } destroy() { this.eventManager.removeAll(); } onFullscreenChange() { this.context.playerManager.customControlsHandler?.updateFullscreenButton(); this.context.playerManager.customControlsHandler?.resetHideTimeout(); } async toggleState() { document.fullscreenElement ? await this.exitFullscreen() : await this.enterFullscreen(); } async enterFullscreen() { try { await this.containerElement?.requestFullscreen(); } catch (e) { this.context.actionLogger.log(`全屏失败: ${e.message}`, LOG.ERROR); } } async exitFullscreen() { if (document.fullscreenElement) await document.exitFullscreen(); } } class SettingsManager { constructor(c) { this.context = c; this.defaults = { autoPlay: true, blacklist: ['github.com', 'stackoverflow.com', 'developer.mozilla.org'], crossFrameSupport: true, defaultPlaybackRate: 1.0, longPressRate: 2.0, enableDecryptionHook: true, enableNetworkInterceptor: true, enablePlayerTakeover: true, isMuted: false, isSmartSlicingEnabled: true, lastVolume: 0.8, maxRetryCount: 1, enableJsonInspector: true, enableSetAttributeHooker: true }; this.config = {}; } async load() { this.config = (await GM_getValue('dmz_v2_settings', {})) ?? {}; if (this.config.floatingPlayerPos) delete this.config.floatingPlayerPos; this.config = { ...this.defaults, ...this.config }; this.context.actionLogger.log("加载完成。", LOG.CONFIG); } async save(nc, al = true) { const oc = { ...this.config }; this.config = { ...this.config, ...nc }; await GM_setValue('dmz_v2_settings', this.config); if (al) { this.context.actionLogger.log("配置已保存", LOG.CONFIG); this.applyLiveSettings(oc, this.config); } } applyLiveSettings(oc, nc) { MANAGED_MODULES.forEach(({ module: m, configKey: k }) => { if (oc[k] !== nc[k]) nc[k] ? m.enable(this.context) : m.disable(this.context); }); this.context.frameCommunicator.showNotification('设置已保存并应用。部分更改可能需要刷新页面才能生效。'); } async reset() { const oc = { ...this.config }, ps = { lastVolume: this.config.lastVolume, isMuted: this.config.isMuted }; this.config = { ...this.defaults, ...ps }; await GM_setValue('dmz_v2_settings', this.config); this.applyLiveSettings(oc, this.config); this.context.actionLogger.log("配置已重置为默认值", LOG.CONFIG); this.context.frameCommunicator.showNotification('已恢复为默认设置。'); return this.config; } async savePlayerPosition(h, k, p) { const ps = await GM_getValue('dmz_player_positions_v2', {}); if (!ps[h]) ps[h] = {}; ps[h][k] = p; await GM_setValue('dmz_player_positions_v2', ps); } async loadPlayerPosition(h, k) { const ps = await GM_getValue('dmz_player_positions_v2', {}); return ps?.[h]?.[k] || null; } } class FrameCommunicator { constructor(c) { this.context = c; this.boundHandleMessage = this.handleMessage.bind(this); this.mainPlayerFrameSource = null; this.mainPlayerFrameOrigin = null; this.mainPlayerIframeElement = null; this.pendingMainPlayerSource = null; this.expectationTimeout = null; } init() { _el(window, 'message', this.boundHandleMessage); } removeListeners() { window.removeEventListener('message', this.boundHandleMessage); } handleMessage(e) { const { actionLogger: al, coreLogic: cl, playerManager: pm } = this.context, d = e.data; if (d?.type === MSG.RAW) { if (IS_TOP_FRAME) { if (this.expectationTimeout) { clearTimeout(this.expectationTimeout); this.expectationTimeout = null; al.log('指挥官:在期望期内收到有效视频信号,乐观激活成功!', LOG.IFRAME); } const cfi = d.payload, sf = Array.from(document.querySelectorAll('iframe')).find(f => f.contentWindow === e.source); if (sf) cl.attachIframeTrigger(sf, e.source); const so = e.origin.replace(/^https?:\/\/(?:www\.)?/, '').split('/')[0].split('.').slice(-2).join('.'); al.log(`收到 🛰[iFrame 信使 @ ${so}] 的新情报,正在提交审查...`, LOG.CORE); cl.addTakeoverCandidate({ ...cfi, sourceName: `${SRC.FRAME} (${cfi.sourceName})`, iframeSource: e.source, iframeOrigin: e.origin }); } return; } if (IS_TOP_FRAME) { if (d?.type === MSG.QUERY) { const sf = Array.from(document.querySelectorAll('iframe')).find(f => f.contentWindow === e.source); if (sf) { if (/(captcha|auth|login|signin|widget|comment|plugin|share|button|ads?|tracker|analytics|pixel|sync|oauth|verify|challenge|static|assets)/i.test(sf.src)) return; const r = sf.getBoundingClientRect(); if (r.width * r.height > 60000) { cl.attachIframeTrigger(sf, e.source); if (!pm.isPlayerActiveOrInitializing && !cl.decisionInProgress) { al.log(`指挥官:甄别通过 (尺寸达标),对目标 [${e.origin}] 执行乐观激活!`, LOG.IFRAME); this.mainPlayerFrameSource = e.source; this.mainPlayerFrameOrigin = e.origin; this.mainPlayerIframeElement = sf; e.source.postMessage({ type: MSG.AUTOPLAY }, e.origin); if (this.expectationTimeout) clearTimeout(this.expectationTimeout); this.expectationTimeout = setTimeout(() => { al.log(`指挥官:期望超时!目标 [${e.origin}] 未能上报视频信号。`, LOG.WARN); if (this.mainPlayerFrameSource === e.source) { this.mainPlayerFrameSource = null; this.mainPlayerFrameOrigin = null; this.mainPlayerIframeElement = null; } this.expectationTimeout = null; }, 5000); } else { this.pendingMainPlayerSource = { source: e.source, origin: e.origin, frameElement: sf }; al.log(`指挥官:目标 [${e.origin}] 忙碌中,已列入候补名单。`, LOG.COMM); } } else al.log(`指挥官:甄别未通过,目标 [${e.origin}] 尺寸过小。`, LOG.COMM); } } else if (d?.type === MSG.LOG) { const { logEntry: le } = d; if (le) { const so = e.origin.replace(/^https?:\/\/(?:www\.)?/, '').split('/')[0].split('.').slice(-2).join('.'); al.log(`[iFrame @ ${so}] ${le.message}`, le.type); } } } else { if (d?.type === MSG.AUTOPLAY) { al.log("士兵:收到指挥官的激活授权!启动轮询点击程序...", LOG.IFRAME); let a = 0; const pc = setInterval(() => { const ss = ['.jw-video', '.vjs-big-play-button', '#player_overlays_play_button', '[class*="play-button"]', '[aria-label*="Play"]', '[data-plyr="play"]', '.isnd.bigger.play'], pb = document.querySelector(ss.join(', ')); if (pb) { al.log(`士兵:轮询成功,在第 ${a + 1} 次尝试中发现目标,执行点击!`, LOG.EXEC); pb.click(); clearInterval(pc); } else { a++; if (a >= 50) { al.log(`士兵:轮询超时。`, LOG.WARN); clearInterval(pc); } } }, 200); } else if (d?.type === MSG.NEUTRALIZE) { try { window.stop(); } catch (e) { } } else if (d?.type === MSG.RESTORE) cl.restorePageToOriginalState(); else if (d?.type === MSG.CLICK) cl.handleRemoteTriggerClick(); } } postToTopFrame(m) { if (!IS_TOP_FRAME) { try { let to = '*'; try { to = window.top.location.origin; } catch (e) { } this.context.actionLogger.log(`向顶层窗口发送消息: ${m.action || m.type}`, LOG.COMM); window.top.postMessage(m, to); } catch (e) { this.context.actionLogger.log(`跨 frame 通信失败: ${e.message}`, LOG.ERROR); } } } showNotification(t, ie = false) { GM_notification({ title: SCRIPT_NAME, text: t, silent: !ie, timeout: ie ? 5000 : 3000 }); } } class StyleManager { constructor(c) { this.context = c; } injectPlayerStyles(sr) { if (!sr.querySelector(`#${C.STYLE}`)) { const s = _ce('style', { id: C.STYLE, textContent: PLAYER_CSS }); sr.appendChild(s); } } } class PlayerManager { constructor(c) { this.context = c; this.hostElement = null; this.shadowRoot = null; this.videoElement = null; this.hlsInstance = null; this.draggableInstance = null; this.fullscreenGestureHandler = null; this.customControlsHandler = null; this.fullscreenHandler = null; this.isPlayerActiveOrInitializing = false; this.isInternalRequest = false; this.currentVideoType = null; this.currentVideoUrl = null; this.currentVideoFormat = null; this.currentPlaybackEngine = null; this.lastM3u8Content = ''; this.currentRequestUrl = null; this.originalPlayerStyle = null; this.dragHandles = []; this.indicatorHideTimeout = null; this.currentVideoResolution = null; this.boundHandleOrientationChange = this._handleOrientationChange.bind(this); } showIndicatorPermanently(v) { clearTimeout(this.indicatorHideTimeout); this.dragHandles.forEach(h => h.classList.toggle('dmz-indicator-visible', v)); } resetIndicatorTimeout() { this.showIndicatorPermanently(true); this.indicatorHideTimeout = setTimeout(() => { this.dragHandles.forEach(h => h.classList.remove('dmz-indicator-visible')); }, 1000); } _createDragHandleBar() { return _ce('div', { className: C.DRAG }, [_ce('div', { className: `${C.SCREW} left` }), _ce('div', { className: C.IND }), _ce('div', { className: `${C.SCREW} right` })]); } _createControlsBar() { return _ce('div', { className: 'dmz-controls-bar' }, [_ce('div', { className: 'dmz-bottom-controls' }, [_ce('span', { className: 'dmz-time-display dmz-current-time', textContent: '00:00' }), _ce('div', { className: 'dmz-time-separator', textContent: '/' }), _ce('span', { className: 'dmz-time-display dmz-total-time', textContent: '00:00' }), _ce('div', { className: 'dmz-spacer' }), _ce('button', { className: 'dmz-control-button dmz-mute-btn', innerHTML: ICONS.VOLUME_ON + ICONS.VOLUME_OFF }), _ce('button', { className: 'dmz-control-button dmz-fullscreen-btn', innerHTML: ICONS.FS_ENTER + ICONS.FS_EXIT }), _ce('div', { className: 'dmz-more-menu-container' }, [_ce('button', { className: 'dmz-control-button dmz-more-btn', innerHTML: ICONS.MORE }), _ce('div', { className: 'dmz-more-menu' }, [_ce('div', { className: 'dmz-menu-item dmz-pip-btn', innerHTML: ICONS.PIP + '画中画' }), _ce('div', { className: 'dmz-menu-item dmz-playback-rate-btn', innerHTML: ICONS.PLAYBACK_RATE + '播放速度正常' }), _ce('div', { className: 'dmz-playback-rates' }, [_ce('div', { className: 'dmz-menu-item', 'data-rate': '0.5', textContent: '0.5x' }), _ce('div', { className: 'dmz-menu-item active', 'data-rate': '1.0', textContent: '正常' }), _ce('div', { className: 'dmz-menu-item', 'data-rate': '1.5', textContent: '1.5x' }), _ce('div', { className: 'dmz-menu-item', 'data-rate': '2.0', textContent: '2.0x' })])])])]), _ce('div', { className: 'dmz-progress-container' }, [_ce('div', { className: 'dmz-progress-bar' }, [_ce('div', { className: 'dmz-progress-rail' }, [_ce('div', { className: 'dmz-progress-buffer' }), _ce('div', { className: 'dmz-progress-played' }), _ce('div', { className: 'dmz-progress-handle' })])])])]); } async createBasePlayerContainer() { this.context.actionLogger.log("创建播放器基础容器...", LOG.PLAYER); this.cleanup(true); this.hostElement = _ce('div', { id: C.ROOT, style: { left: '50%', top: '0px', width: '280px', height: C.H_PX, gap: '0px', transform: 'translateX(-50%) translateY(0px)' } }); const pp = (e) => { e.stopPropagation(); if (['touchmove', 'wheel'].includes(e.type) && e.cancelable) e.preventDefault(); if (e.type === 'contextmenu') e.preventDefault(); }; ['touchstart', 'touchmove', 'touchend', 'mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'contextmenu', 'wheel'].forEach(e => this.hostElement.addEventListener(e, pp, { passive: false })); _el(window, 'orientationchange', this.boundHandleOrientationChange); document.body.appendChild(this.hostElement); this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' }); this.context.styleManager.injectPlayerStyles(this.shadowRoot); const cb = _ce('div', { className: C.CLOSE, textContent: '×' }), hc = (e) => { Utils.stopEvent(e); this.context.coreLogic.restorePageToOriginalState(); this.cleanup(); }; cb.addEventListener('touchend', hc); cb.addEventListener('click', hc); const sc = this.context.settingsManager.config, av = _ce('video', { id: C.PLAYER, playsInline: true, autoplay: sc.autoPlay }); this.videoElement = av; av.volume = sc.lastVolume; av.playbackRate = sc.defaultPlaybackRate; if (sc.isMuted) { av.muted = true; this.context.actionLogger.log("应用用户偏好:静音。", LOG.PLAYER); } else { av.muted = true; av.dataset.dmzStrategicMute = 'true'; this.context.actionLogger.log("应用用户偏好:策略性静音已启动。", LOG.PLAYER); } const vw = _ce('div', { className: C.WRAP, innerHTML: `
${ICONS.BRIGHTNESS}
${ICONS.VOLUME_SIDE}
${ICONS.FORWARD}${ICONS.REWIND}
` }); vw.prepend(cb, av); vw.append(this._createControlsBar()); vw.addEventListener('touchstart', () => this.resetIndicatorTimeout(), { passive: true }); vw.addEventListener('mousemove', () => this.resetIndicatorTimeout()); const hb = this._createDragHandleBar(), fb = this._createDragHandleBar(); this.dragHandles = [hb, fb]; const c = _ce('div', {}, [hb, vw, fb]); this.shadowRoot.appendChild(c); this.draggableInstance = new Draggable(this.context, this.hostElement, this.dragHandles, this); this.customControlsHandler = new CustomControlsHandler(this.context, vw, av); this.customControlsHandler.init(); this.fullscreenGestureHandler = new FullscreenGestureHandler(this.context, vw, av); this.fullscreenGestureHandler.enable(); this.fullscreenHandler = new FullscreenHandler(this.context, vw); this.activeInteraction = null; this.boundHandlePointerDown = this.handlePointerDown.bind(this); this.boundHandlePointerMove = this.handlePointerMove.bind(this); this.boundHandlePointerUp = this.handlePointerUp.bind(this); vw.addEventListener('pointerdown', this.boundHandlePointerDown, { capture: true }); this.context.actionLogger.log("所有模块初始化完成。", LOG.PLAYER); return { video: av, videoWrapper: vw }; } async _applyOrientationLayout() { if (!this.hostElement || !this.videoElement || !this.isPlayerActiveOrInitializing || !this.videoElement.videoWidth) return; const il = screen.orientation.type.includes('landscape'), iv = this.videoElement.videoHeight > this.videoElement.videoWidth, vok = iv ? 'verticalVideo' : 'horizontalVideo', pok = il ? 'landscape' : 'portrait', sp = await this.context.settingsManager.loadPlayerPosition(window.location.hostname, `${pok}_${vok}`); if (!il) { if (this.originalPlayerStyle) { Object.assign(this.hostElement.style, this.originalPlayerStyle); this.originalPlayerStyle = null; } this.hostElement.style.width = iv ? '60vw' : '100vw'; this.hostElement.style.height = ''; if (sp && sp.top) { const l = parseFloat(sp.left), t = parseFloat(sp.top); if (l > window.innerWidth - 30 || t > window.innerHeight - 30 || l < -50 || t < -50) { this.hostElement.style.top = iv ? '192px' : '60px'; this.hostElement.style.left = '50%'; this.hostElement.style.transform = 'translateX(-50%)'; } else { this.hostElement.style.top = sp.top; this.hostElement.style.left = sp.left; this.hostElement.style.transform = ''; } } else { this.hostElement.style.top = iv ? '192px' : '60px'; this.hostElement.style.left = '50%'; this.hostElement.style.transform = 'translateX(-50%)'; } return; } if (il) { if (!this.originalPlayerStyle) this.originalPlayerStyle = { left: this.hostElement.style.left, top: this.hostElement.style.top, width: this.hostElement.style.width, height: this.hostElement.style.height, transform: this.hostElement.style.transform }; this.context.actionLogger.log('应用横屏布局。', LOG.UI); this.hostElement.style.transform = ''; if (sp) { const l = parseFloat(sp.left), t = parseFloat(sp.top); if (l > window.innerWidth - 30 || t > window.innerHeight - 30 || l < -50 || t < -50) { this.hostElement.style.top = '20px'; this.hostElement.style.left = '20px'; } else { this.hostElement.style.top = sp.top; this.hostElement.style.left = sp.left; } } else { this.hostElement.style.top = '20px'; this.hostElement.style.left = '20px'; } const va = this.videoElement.videoWidth / this.videoElement.videoHeight; if (iv) { this.hostElement.style.height = 'calc(100vh - 40px)'; this.hostElement.style.width = `calc((100vh - 40px) * ${va})`; } else { const dw = '45vw'; this.hostElement.style.width = dw; this.hostElement.style.height = `calc(${dw} / ${va})`; } } } _handleOrientationChange() { this.hostElement.classList.add('dmz-no-transition'); this._applyOrientationLayout(); requestAnimationFrame(() => this.hostElement?.classList.remove('dmz-no-transition')); } async _preparePlaybackSession(vt) { const { settingsManager: sm } = this.context; this.isPlayerActiveOrInitializing = true; this.currentVideoType = vt; if (!IS_TOP_FRAME && sm.config.crossFrameSupport) { this.isPlayerActiveOrInitializing = false; return null; } return await this.createBasePlayerContainer(); } async revealPlayer(v, vw) { const { actionLogger: al, settingsManager: sm, coreLogic: cl, frameCommunicator: fc } = this.context, mi = fc.mainPlayerIframeElement; if (mi && mi.style.visibility !== 'hidden') { al.log(`播放器渲染成功,执行终极中和:保留空间并隐藏Iframe。`, LOG.NEUT); cl.hiddenIframeElement = mi; cl.originalIframeVisibilityStyle = mi.style.visibility; cl.originalIframePointerEventsStyle = mi.style.pointerEvents; cl.originalIframeSrc = mi.src; mi.style.visibility = 'hidden'; mi.style.pointerEvents = 'none'; } if (!v.videoWidth || !v.videoHeight) { al.log(`视频尺寸未就绪,启动严格校验模式...`, LOG.REVEAL); try { await new Promise((res, rej) => { let a = 0; const i = setInterval(() => { if (!this.hostElement) { clearInterval(i); rej(new Error('ABORT_TASK')); return; } if (v.error || v.networkState === 3) { clearInterval(i); rej(new Error('视频源已失效(NetworkState=3)或加载错误')); return; } if (v.videoWidth > 0 && v.videoHeight > 0) { clearInterval(i); res(); } else { a++; if (a === 5) v.play().catch(() => { }); if (a === 40 && v.readyState === 0) v.load(); if (a > 160) { clearInterval(i); rej(new Error(`等待视频尺寸超时 (8s)`)); } } }, 50); }); al.log(`轮询成功,视频尺寸已确认: ${v.videoWidth}x${v.videoHeight}。`, LOG.REVEAL); } catch (e) { if (e.message === 'ABORT_TASK') { al.log(`[渲染中止] 任务被中断 (用户操作/页面切换)。`, LOG.LIFE); this.cleanup(); } else { al.log(`[渲染失败] ${e.message} -> 正在尝试备用信号...`, LOG.WARN); cl.reportPlaybackFailure({ failedUrl: this.currentVideoUrl, reason: e.message }); } return; } } this.currentVideoResolution = `${v.videoWidth}x${v.videoHeight}`; al.log(`视频分辨率已确认: ${this.currentVideoResolution}`, LOG.REVEAL); cl._cleanupSandbox(); al.log(`视频元数据就绪,画面开始呈现 (${v.videoWidth}x${v.videoHeight})。`, LOG.REVEAL); if (!this.hostElement) { al.log("播放器显示失败:宿主元素不存在。", LOG.WARN); this.cleanup(); return; } this.hostElement.classList.add('dmz-no-transition'); await this._applyOrientationLayout(); this.hostElement.style.gap = '3px'; vw.style.flexGrow = '1'; v.style.opacity = '1'; void this.hostElement.offsetHeight; requestAnimationFrame(() => { if (this.hostElement) { this.hostElement.classList.remove('dmz-no-transition'); this.hostElement.classList.add('dmz-visible'); requestAnimationFrame(() => { if (vw) { const w = this.hostElement.clientWidth, h = w * (v.videoHeight / v.videoWidth); vw.style.maxHeight = `${Math.min(h, window.innerHeight * 0.8)}px`; } }); } }); const hp = async () => { if (sm.config.autoPlay) { try { const pp = v.play(); if (pp !== undefined) pp.catch(e => { al.log(`⏸️ 自动播放被阻断: ${e.message}`, LOG.WARN); }); al.log("▶️ 自动播放已启动。", LOG.PLAYER); this.customControlsHandler?.hideControls(); this.showIndicatorPermanently(false); } catch (e) { al.log(`⏸️ 自动播放失败: ${e.message},请手动点击播放。`, LOG.WARN); this.customControlsHandler?.resetHideTimeout(); this.resetIndicatorTimeout(); } } else { al.log("🚫 自动播放已禁用,请手动点击播放。", LOG.PLAYER); v.pause(); this.customControlsHandler?.resetHideTimeout(); this.resetIndicatorTimeout(); } }; hp(); } async play(vd) { const { coreLogic: cl, actionLogger: al } = this.context; al.log("指令已接收,准备渲染视频内容。", LOG.PLAYER); const im = typeof vd === 'object', vt = im ? 'm3u8' : 'normal'; if (im) { this.currentVideoUrl = vd.finalUrl; this.lastM3u8Content = vd.original; } else { this.currentVideoUrl = vd; this.lastM3u8Content = ''; } this.currentRequestUrl = this.currentVideoUrl; const ce = await this._preparePlaybackSession(vt); if (!ce) { cl.sendPlayCommand(vt, vd); return; } const { video: v, videoWrapper: vw } = ce, or = () => this.revealPlayer(v, vw); v.addEventListener('loadedmetadata', or, { once: true }); if (im) this.playM3U8(vd, v, true); else this.playNormalVideo(vd, v); } playM3U8(md, ve, ifp) { const { settingsManager: sm, diagnosticsTool: dt, actionLogger: al, coreLogic: cl } = this.context; this.currentVideoFormat = { name: "M3U8", type: "流式传输" }; const qm = md.finalUrl.match(/\/(\d+p)\//); cl.currentPlayingQuality = qm ? qm[1] : 'unknown'; al.log(`信息:当前播放清晰度已记录为: ${cl.currentPlayingQuality}`, LOG.PLAYER); const oc = md.original ?? ''; if (oc.includes('#EXT-X-KEY') && !oc.includes('METHOD=NONE')) dt.playbackHealth.key.status = 'pending'; else dt.playbackHealth.key.status = 'not_encrypted'; const ctp = md.processed, du = `data:application/vnd.apple.mpegurl;base64,${btoa(unescape(encodeURIComponent(ctp)))}`; dt.lastProcessedM3u8 = ctp; try { this.isInternalRequest = true; if (Hls.isSupported()) { this.currentPlaybackEngine = 'HLS.js'; const hls = new Hls({ maxBufferLength: 120, maxBufferSize: 200 * 1000 * 1000, maxMaxBufferLength: 120, startFragPrefetch: true, fragLoadTimeOut: 4000, manifestLoadTimeOut: 4000, lowLatencyMode: false, debug: false, fetchSetup: function (c, i) { i.headers = i.headers || {}; i.headers.Origin = new URL(window.location.href).origin; i.headers.Referer = window.location.href; i.headers.Accept = '*/*'; i.headers['Accept-Language'] = 'zh-CN,zh;q=0.9,en;q=0.8'; i.headers['Sec-Fetch-Dest'] = 'empty'; i.headers['Sec-Fetch-Mode'] = 'cors'; i.headers['Sec-Fetch-Site'] = 'cross-site'; i.credentials = 'include'; i.redirect = 'follow'; i.mode = 'cors'; return new Request(c.url, i); } }); this.hlsInstance = hls; hls.loadSource(du); hls.attachMedia(ve); let cne = 0; hls.on(Hls.Events.MANIFEST_PARSED, () => { al.log("清单解析成功,准备加载视频数据。", LOG.HLS); dt.playbackHealth.manifest.status = 'success'; cne = 0; if (ifp || sm.config.autoPlay) ve.play().catch(() => { }); }); hls.on(Hls.Events.FRAG_LOADED, () => { dt.playbackHealth.segments.status = 'success'; cne = 0; }); hls.on(Hls.Events.KEY_LOADED, () => { dt.playbackHealth.key.status = 'success'; }); hls.on(Hls.Events.ERROR, (e, d) => { if (!d.fatal) return; if (d.type === Hls.ErrorTypes.NETWORK_ERROR) { cne++; if (cne <= 5) { const dl = cne * 1000; al.log(`⚠️ 网络波动 (第 ${cne}/5 次尝试),${dl / 1000}秒后自动重连...`, LOG.WARN); if (this.hlsInstance) setTimeout(() => { if (this.hlsInstance) this.hlsInstance.startLoad(); }, dl); return; } else al.log(`❌ 网络错误重试超过上限 (5次),判定为源失效,停止播放。`, LOG.ERROR); } if (d.type === Hls.ErrorTypes.MEDIA_ERROR) { al.log("媒体解码异常,正在尝试恢复...", LOG.HLS); hls.recoverMediaError(); return; } al.log(`HLS 发生无法挽回的错误 (type: ${d.type}),播放终止。`, LOG.ERROR); dt.captureFatalSnapshot({ event: 'HLS_FATAL_ERROR', data: d }); this.context.coreLogic.reportPlaybackFailure(); hls.destroy(); this.cleanup(); }); } else { al.log("HLS.js 不支持M3U8播放。", LOG.ERROR); this.cleanup(); } } finally { setTimeout(() => { this.isInternalRequest = false; }, 500); } } playNormalVideo(vu, ve) { this.currentPlaybackEngine = '浏览器原生'; const { diagnosticsTool: dt } = this.context; if (!this.currentVideoFormat) this.currentVideoFormat = Utils.getVideoFormat(vu); this.currentVideoUrl = vu; ve.src = vu; ve.addEventListener('loadeddata', () => { dt.playbackHealth.media.status = 'success'; }, { once: true }); ve.addEventListener('error', () => { dt.playbackHealth.media.status = 'error'; this.context.coreLogic.reportPlaybackFailure(); }, { once: true }); } cleanup(iic = false) { window.removeEventListener('orientationchange', this.boundHandleOrientationChange); this.originalPlayerStyle = null; const { actionLogger: al, diagnosticsTool: dt, coreLogic: cl } = this.context; al.log(`开始清理播放器... (内部调用: ${iic})`, LOG.PLAYER); document.querySelectorAll('.dmz-iframe-remote-trigger').forEach(e => { e.style.display = 'flex'; if (e.dmzUpdatePos) e.dmzUpdatePos(); }); if (!iic) cl.stopPersistentEnforcer(); try { if (this.indicatorHideTimeout) clearTimeout(this.indicatorHideTimeout); if (this.hlsInstance) this.hlsInstance.destroy(); } catch (e) { al.log(`清理HLS或Blob时发生错误: ${e.message}`, LOG.ERROR); } finally { this.hlsInstance = null; this.indicatorHideTimeout = null; } if (this.hostElement) { try { this.draggableInstance?.destroy(); this.fullscreenGestureHandler?.deactivate(); this.customControlsHandler?.destroy(); this.fullscreenHandler?.destroy(); this.hostElement.remove(); } catch (e) { al.log(`清理播放器UI模块时发生错误: ${e.message}`, LOG.ERROR); } finally { this.hostElement = null; this.shadowRoot = null; this.videoElement = null; this.draggableInstance = null; this.fullscreenGestureHandler = null; this.customControlsHandler = null; this.fullscreenHandler = null; } } this.currentVideoResolution = null; if (!iic) { try { if (cl.backupBufferTimer) { clearTimeout(cl.backupBufferTimer); cl.backupBufferTimer = null; } cl.isBufferingBackupCandidates = false; cl.bufferedBackupCandidates.clear(); dt.lastProcessedM3u8 = null; dt.resetPlaybackHealth(); dt.resetSlicingReport(); dt.takeoverEvidence = { sources: [], url: null }; } catch (e) { al.log(`清理核心状态时发生错误: ${e.message}`, LOG.ERROR); } finally { this.isPlayerActiveOrInitializing = false; this.currentVideoType = null; this.currentVideoUrl = null; this.currentVideoFormat = null; this.currentPlaybackEngine = null; this.lastM3u8Content = ''; this.currentRequestUrl = null; al.log("播放器状态锁已释放", LOG.LOCK); if (cl.hiddenIframeElement) { al.log(`检测到被隐藏的Iframe,开始恢复并重新加载...`, LOG.LIFE); if (cl.originalIframeSrc) cl.hiddenIframeElement.src = cl.originalIframeSrc; cl.hiddenIframeElement.style.visibility = cl.originalIframeVisibilityStyle; cl.hiddenIframeElement.style.pointerEvents = cl.originalIframePointerEventsStyle; cl.hiddenIframeElement = null; cl.originalIframeVisibilityStyle = ''; cl.originalIframePointerEventsStyle = ''; cl.originalIframeSrc = ''; } } } this.isInternalRequest = false; } handlePointerDown(e) { if (!e.isPrimary || this.activeInteraction) return; const t = e.target; if (t.closest(`.${C.DRAG}`)) this.activeInteraction = 'drag'; else if (t.closest('.dmz-progress-bar')) this.activeInteraction = 'seek'; else if (!t.closest('.dmz-controls-bar, .dmz-close-button')) this.activeInteraction = 'gesture'; else return; Utils.stopEvent(e); switch (this.activeInteraction) { case 'drag': this.draggableInstance.handleDragStart(e); break; case 'seek': this.customControlsHandler.isSeeking = true; this.customControlsHandler.updateSeekUI(e.clientX); break; case 'gesture': this.fullscreenGestureHandler.onPointerDown(e); break; } _el(document, 'pointermove', this.boundHandlePointerMove, { capture: true }); _el(document, 'pointerup', this.boundHandlePointerUp, { capture: true }); _el(document, 'pointercancel', this.boundHandlePointerUp, { capture: true }); } handlePointerMove(e) { if (!e.isPrimary || !this.activeInteraction) return; Utils.stopEvent(e); switch (this.activeInteraction) { case 'drag': this.draggableInstance.handleDragMove(e); break; case 'seek': this.customControlsHandler.updateSeekUI(e.clientX); break; case 'gesture': this.fullscreenGestureHandler.onPointerMove(e); break; } } handlePointerUp(e) { if (!e.isPrimary || !this.activeInteraction) return; Utils.stopEvent(e); document.removeEventListener('pointermove', this.boundHandlePointerMove, { capture: true }); document.removeEventListener('pointerup', this.boundHandlePointerUp, { capture: true }); document.removeEventListener('pointercancel', this.boundHandlePointerUp, { capture: true }); switch (this.activeInteraction) { case 'drag': this.draggableInstance.handleDragEnd(e); break; case 'seek': { const nt = this.customControlsHandler.updateSeekUI(e.clientX); if (this.videoElement && !isNaN(nt)) this.videoElement.currentTime = nt; this.customControlsHandler.isSeeking = false; break; } case 'gesture': this.fullscreenGestureHandler.onPointerUp(e); break; } this.activeInteraction = null; } } class InfoPanelManager { constructor(c) { this.context = c; } generateDiagnosticsContent() { const cb = _ce('button', { className: 'copy-report-btn', title: '复制完整诊断报告', innerHTML: '📋 复制报告' }), c = _ce('div', { className: 'dmz-panel-pane-content', innerHTML: `

[广告过滤]净化报告

净化结果
分析摘要

视频与播放诊断

接管方式
视频格式
播放分辨率
来源地址

播放健康度诊断

播放列表 (M3U8)
内容解密 (Key)
视频流 (TS)
媒体文件加载
▼ 展开开发者日志
` }); c.prepend(cb); const dlc = c.querySelector('.developer-logs'), dt = c.querySelector('.details-toggle'); dt.addEventListener('click', () => { const v = dlc.style.display !== 'none'; dlc.style.display = v ? 'none' : 'block'; dt.textContent = (!v ? '▲ 收起' : '▼ 展开') + '开发者日志'; }); cb.addEventListener('click', () => { const rt = this.generateFullReportText(); Utils.copyToClipboard(rt, () => Utils.showButtonFeedback(cb, { successText: '✔ 已复制!', duration: 2000 })); }); return c; } generateFullReportText() { const r = this.context.unifiedPanelManager?.elements?.shadowRoot; if (!r) return "无法生成报告:面板未初始化。"; const gt = (s) => r.querySelector(s)?.innerText.trim() ?? 'N/A', gv = (s) => r.querySelector(s)?.value ?? 'N/A', nl = '\n', dbl = '\n\n'; return `### 📊 ${SCRIPT_NAME} 诊断报告 ###${dbl}**版本:** ${SCRIPT_VERSION}${nl}**页面URL:** ${window.location.href}${dbl}--- [广告过滤]净化报告 ---${dbl}**净化结果:** ${gt('[data-info="ad-filter"]')}${nl}**分析摘要:**${nl}${gt('[data-info="ad-analysis-result"]').replace(/•/g, '* ')}${nl}${dbl}--- 视频与播放诊断 ---${dbl}**接管方式:** ${gt('[data-info="takeover"]')}${nl}**视频来源:** ${this.context.playerManager?.currentVideoUrl ?? 'N/A'}${nl}**播放健康度 - 播放列表:** ${gt('[data-info="health-m3u8"]')}${nl}**播放健康度 - 内容解密:** ${gt('[data-info="health-key"]')}${nl}**播放健康度 - 视频流:** ${gt('[data-info="health-ts"]')}${nl}${dbl}--- M3U8 原始情报 ---${dbl}${gv('[data-info="m3u8-raw"]')}${dbl}--- M3U8 净化后情报 ---${dbl}${gv('[data-info="m3u8-processed"]')}${dbl}--- 开发者事件时间轴 ---${dbl}${gt('[data-info="dev-log"]')}`; } _getStatusHtml(s, st, et, pt, ec = '') { switch (s) { case 'success': return `✅ ${st}`; case 'not_encrypted': return `✅ ${st}`; case 'error': return `❌ ${et} ${ec ? `(错误: ${ec})` : ''}`; default: return `⏳ ${pt}`; } } update() { const r = this.context.unifiedPanelManager?.elements?.shadowRoot; if (!r || !this.context.playerManager || !this.context.diagnosticsTool) return; const qs = (s) => r.querySelector(s), { playerManager: pm, diagnosticsTool: dt, settingsManager: sm } = this.context, h = dt.playbackHealth; let i = '🕵️', t = '正在页面上巡逻,待命中...'; if (h.manifest.status === 'error') { i = '❌'; t = `视频目录加载失败 (错误: ${h.manifest.code}),无法播放`; } else if (h.key.status === 'error') { i = '❌'; t = `视频密钥获取失败 (错误: ${h.key.code}),无法解密`; } else if (h.media.status === 'error' && h.media.reason) { i = '❌'; t = `媒体解码失败 (原因: ${h.media.reason})`; } else if (h.segments.status === 'error') { i = '❌'; t = `视频数据加载失败 (已达最大重试)`; } else if (pm.isPlayerActiveOrInitializing && pm.videoElement?.readyState > 2) { i = '✅'; t = '视频正在流畅播放中!'; } else if (pm.isPlayerActiveOrInitializing) { i = '⏳'; t = '正在缓冲视频...'; } qs('[data-info="status-icon"]').innerHTML = `${i}`; qs('[data-info="status-text"]').textContent = t; const rp = dt.slicingReport ?? {}; let vf = pm.currentVideoFormat ?? { name: '未知', type: '' }; if (pm.lastM3u8Content && vf.name !== 'M3U8') vf = { name: 'M3U8', type: '流式传输' }; const af = qs('[data-info="ad-filter"]'), aar = qs('[data-info="ad-analysis-result"]'); if (!sm.config.isSmartSlicingEnabled) { af.innerHTML = `“广告过滤”系统未启用`; aar.innerHTML = `已在设置中关闭,所有净化模块将不会运行。`; } else if (pm.isPlayerActiveOrInitializing) { if (vf.name === 'M3U8') { if (rp.slicedSegments > 0) { const sd = rp.slicedDuration ?? 0; let dt = ''; if (sd > 0) { let fd; if (sd < 60) fd = `${sd.toFixed(1)}秒`; else { const m = Math.floor(sd / 60), rs = Math.round(sd % 60); fd = `${m}分${rs}秒`; } dt = `,总计时长约 ${fd}`; } af.innerHTML = `🛡️〖任务完成〗 已为您拦截 ${rp.slicedSegments} 个广告片段${dt}。`; let ah = '📄〖广告过滤〗 模块已激活,净化协议启动:'; if (rp.slicedTimeRanges?.length > 0) { rp.slicedTimeRanges.sort((a, b) => a.start - b.start); ah += '
切除时间点:
'; } ah += '[广告过滤]模块已全面接管,为您带来无干扰的沉浸式观看体验。'; aar.innerHTML = ah; } else { af.innerHTML = `🛡️〖无需净化〗 视频流纯净。`; aar.innerHTML = `📑〖广告过滤〗模块已完成扫描,未在视频流中发现任何广告特征。确认为纯净内容,请放心观看。`; } } else { af.innerHTML = `🛡️( 常规文件〖无需净化〗)`; aar.innerHTML = `📑〖广告过滤〗模块仅适用于 M3U8 流媒体。当前 〖${vf.name}〗 格式为单一文件,已为您直接播放。`; } } else { af.innerHTML = `⏳ 等待视频...`; aar.innerHTML = `捕获到视频后,〖广告过滤〗模块将自动启动。`; } qs('[data-info="takeover"]').textContent = Array.from(dt.takeoverEvidence.sources).join(' + ') || 'N/A'; qs('[data-info="format"]').textContent = pm.currentVideoFormat ? `${pm.currentVideoFormat.name} (${pm.currentVideoFormat.type})` : '未知'; qs('[data-info="resolution"]').textContent = pm.currentVideoResolution || '等待视频加载...'; const ue = qs('[data-info="url"]'), fu = pm.currentVideoUrl ?? 'N/A'; ue.innerHTML = ''; ue.appendChild(document.createTextNode((fu.length > 80 ? '...' + fu.slice(-80) : fu) + ' ')); if (fu !== 'N/A') ue.appendChild(Utils.createCopyButtonWithFeedback(fu, '复制')); const im = vf.name === 'M3U8'; qs('[data-info-container="health-m3u8"]').style.display = 'flex'; qs('[data-info-container="health-m3u8"]').style.flexDirection = 'column'; qs('[data-info-container="health-m3u8"]').style.gap = '10px'; qs('[data-info-container="health-normal"]').style.display = 'none'; if (pm.isPlayerActiveOrInitializing) { if (im) { qs('[data-info="health-m3u8"]').innerHTML = this._getStatusHtml(h.manifest.status, '播放列表已就绪', '播放列表加载失败', '正在请求播放列表...', h.manifest.code); qs('[data-info="health-key"]').innerHTML = this._getStatusHtml(h.key.status, h.key.status === 'not_encrypted' ? '内容未加密,无需解密' : '密钥获取成功', '密钥获取失败', '正在分析加密...', h.key.code); qs('[data-info="health-ts"]').innerHTML = this._getStatusHtml(h.segments.status, '视频流缓冲中 (加载正常)', `视频流加载不畅`, '等待视频流加载...'); } else { qs('[data-info-container="health-m3u8"]').style.display = 'none'; qs('[data-info-container="health-normal"]').style.display = 'flex'; qs('[data-info-label="health-normal-label"]').textContent = `〖${vf.name}〗 文件加载状态`; qs('[data-info="health-normal"]').innerHTML = this._getStatusHtml(h.media.status, '媒体文件已加载', '媒体文件加载失败', '正在加载媒体文件...'); } } else { qs('[data-info="health-m3u8"]').textContent = '⏳ 待命中'; qs('[data-info="health-key"]').textContent = '⏳ 待命中'; qs('[data-info="health-ts"]').textContent = '⏳ 待命中'; } const mr = qs('[data-info="m3u8-raw"]'); mr.value = pm.lastM3u8Content ?? '暂未捕获到M3U8情报。'; qs('[data-info-container="m3u8-processed"]').style.display = im ? 'flex' : 'none'; const mp = qs('[data-info="m3u8-processed"]'); mp.value = dt.lastProcessedM3u8 ?? '暂无处理后内容。'; const mrc = qs('[data-info="m3u8-raw-controls"]'); mrc.innerHTML = ''; if (mr.value && !mr.value.includes('暂未捕获')) mrc.appendChild(Utils.createCopyButtonWithFeedback(mr.value, '复制')); const mpc = qs('[data-info="m3u8-processed-controls"]'); mpc.innerHTML = ''; if (im && mp.value && !mp.value.includes('暂无')) mpc.appendChild(Utils.createCopyButtonWithFeedback(mp.value, '复制')); } renderDeveloperLog() { const r = this.context.unifiedPanelManager?.elements?.shadowRoot; if (!r || !this.context.diagnosticsTool) return; const tl = this.context.diagnosticsTool.eventTimeline; if (tl.length > 0) { const ll = tl[tl.length - 1], cs = `${tl.length}_${ll.sequence}`; if (this._lastLogSignature === cs) return; this._lastLogSignature = cs; } const qs = (s) => r.querySelector(s), { diagnosticsTool: dt } = this.context, dl = qs('[data-info="dev-log"]'); if (dl) dl.innerHTML = dt.generateDeveloperReport(); const dlc = qs('[data-info="dev-log-controls"]'); if (dlc) { dlc.innerHTML = ''; const b = document.createElement('button'); b.className = 'copy-btn'; b.textContent = '复制日志 (所见即所得)'; b.addEventListener('click', () => { const vt = dl.innerText; Utils.copyToClipboard(vt, () => Utils.showButtonFeedback(b, { successText: '✔ 已复制!' })); }); dlc.appendChild(b); } } } class CoreLogic { constructor(c) { this.failedCandidateKeys = new Set(); this.context = c; this.lastSuccessfulUrl = null; this.decisionMade = false; this.decisionInProgress = false; this.takeoverCandidates = new Map(); this.lastCandidatesBackup = new Map(); this.lastPostedUrl = null; this.currentRequestId = 0; this.activeNeutralizers = new Set(); this.currentPlayingQuality = null; this.iframeSandbox = null; this.sandboxReloadTimer = null; this.backupBufferTimer = null; this.isBufferingBackupCandidates = false; this.bufferedBackupCandidates = new Map(); this.hiddenIframeElement = null; this.originalIframeSrc = ''; this.neutralizedMedia = new Set(); this.enforcerInterval = null; this.passiveCandidates = new Map(); this.sandboxCountdownTimer = null; this.hasTriggeredSandbox = false; } _isLikelyVideoPage() { const p = window.location.pathname.toLowerCase() + window.location.search.toLowerCase(); if (Utils.isHighNoisePage()) return false; if (/(play|video|watch|view|episode|anime|movie|drama|vod|chapter|section)/i.test(p)) return true; if (/(\/|-|_)\d+\.html(\?|$)/i.test(p)) return true; if (/(\/)(v|p|play)(\/|$)/i.test(p)) return true; if (document.querySelector('video, iframe')) return true; return false; } reloadInSandbox() { if (new URLSearchParams(window.location.search).has('dmz_sandbox')) return; if (this.hasTriggeredSandbox) { this.context.actionLogger.log(`[沙箱系统] 已执行过重载但仍未成功,停止尝试。`, LOG.WARN); return; } this.hasTriggeredSandbox = true; let tu = window.location.href, lm = "正在后台执行静默重载(全页模式)..."; try { const pis = Array.from(document.querySelectorAll('iframe')).filter(f => { const r = f.getBoundingClientRect(); return r.width > 300 && r.height > 200 && f.src && f.src.startsWith('http'); }); if (pis.length > 0) { const bi = pis.sort((a, b) => (b.offsetWidth * b.offsetHeight) - (a.offsetWidth * a.offsetHeight))[0]; if (bi && !bi.src.includes('google') && !bi.src.includes('disqus')) { tu = bi.src; lm = `正在后台执行静默重载(精准模式),目标: ${tu.slice(-40)}`; } } } catch (e) { } this.context.actionLogger.log(`🚀 [沙箱系统] ${lm}`, LOG.IFRAME); this.iframeSandbox = document.createElement('iframe'); Object.assign(this.iframeSandbox.style, { display: 'none', width: '0', height: '0', border: 'none', position: 'absolute', visibility: 'hidden', zIndex: '-1' }); const nu = new URL(tu); nu.searchParams.set('dmz_sandbox', 'true'); this.iframeSandbox.src = nu.href; document.body.appendChild(this.iframeSandbox); this.sandboxReloadTimer = setTimeout(() => { if (this.iframeSandbox) { this.context.actionLogger.log(`[沙箱系统] 探测超时,未发现新信号。`, LOG.WARN); this._cleanupSandbox(); } }, 15000); } startSandboxCountdown() { if (new URLSearchParams(window.location.search).has('dmz_sandbox') || this.context.playerManager.isPlayerActiveOrInitializing || !this._isLikelyVideoPage()) { return; } this.context.actionLogger.log("⏳ [沙箱系统] 视频页判定通过,启动 2秒 信号搜寻倒计时...", LOG.CORE); this.sandboxCountdownTimer = setTimeout(() => { if (this.context.playerManager.isPlayerActiveOrInitializing || this.decisionInProgress) return; if (document.readyState !== 'complete') { this.context.actionLogger.log("⏳ [沙箱系统] 检测到页面仍在加载中(网速较慢),延长等待时间...", LOG.CORE); this.sandboxCountdownTimer = setTimeout(() => { if (this.context.playerManager.isPlayerActiveOrInitializing || this.decisionInProgress) return; this.context.actionLogger.log("⌛ [沙箱系统] 宽限期结束仍无信号,触发重载。", LOG.EXEC); this.reloadInSandbox(); }, 3000); return; } this.context.actionLogger.log("⌛ [沙箱系统] 2秒内无有效信号且页面加载完毕,触发重载。", LOG.EXEC); this.reloadInSandbox(); }, 2000); } cancelSandboxCountdown(r) { if (this.sandboxCountdownTimer) { clearTimeout(this.sandboxCountdownTimer); this.sandboxCountdownTimer = null; this.context.actionLogger.log(`⏳ [沙箱系统] 倒计时已取消 (${r})。`, LOG.CORE); } } createTriggerOverlay(t, oa, od, ip = false) { const o = _ce('div', { className: ip ? 'dmz-switch-overlay' : 'dmz-iframe-remote-trigger', style: { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', background: 'rgba(0,0,0,0.5)', display: ip ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', zIndex: '10', opacity: ip ? '1' : '0', pointerEvents: ip ? 'auto' : 'none', transition: 'opacity 0.2s linear', userSelect: 'none', WebkitUserSelect: 'none', WebkitTouchCallout: 'none' }, innerHTML: `
` }), cb = _ce('div', { style: { position: 'absolute', top: '0', left: '0', width: '30px', height: '30px', background: 'rgba(0,0,0,0.6)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', zIndex: '100', borderBottomRightRadius: '8px', fontSize: '20px', fontWeight: 'bold' }, textContent: '×', title: '关闭触发器' }); cb.addEventListener('click', (e) => { Utils.stopEvent(e); od(o); }); o.appendChild(cb); let lpt; const slp = (e) => { if ((e.type === 'mousedown' && e.button !== 0) || (e.type === 'touchstart' && e.touches.length > 1)) return; const ph = this.context.playerManager.hostElement; if (ph && ph.classList.contains('dmz-visible')) return; lpt = setTimeout(() => { this.restorePageToOriginalState(); if (navigator.vibrate) navigator.vibrate(50); }, 1000); }, clp = () => clearTimeout(lpt); o.addEventListener('contextmenu', (e) => { Utils.stopEvent(e); }); o.addEventListener('mousedown', slp); o.addEventListener('touchstart', slp, { passive: true }); ['mouseup', 'touchend', 'touchcancel', 'mouseleave'].forEach(e => o.addEventListener(e, clp)); o.addEventListener('click', (e) => { Utils.stopEvent(e); clearTimeout(lpt); oa(e, o); }); return o; } startPersistentEnforcer() { if (this.enforcerInterval) return; const { actionLogger: al } = this.context; al.log("启动【持久化压制器】,确保原生播放器静默。", LOG.NEUT); this.enforcerInterval = setInterval(() => { if (this.neutralizedMedia.size === 0) { this.stopPersistentEnforcer(); return; } this.neutralizedMedia.forEach(m => { if (!document.body.contains(m)) this.neutralizedMedia.delete(m); else if (m.paused === false || m.muted === false) { m.pause(); m.muted = true; } }); }, 200); } stopPersistentEnforcer() { if (!this.enforcerInterval) return; const { actionLogger: al } = this.context; al.log("停止【持久化压制器】。", LOG.NEUT); clearInterval(this.enforcerInterval); this.enforcerInterval = null; this.neutralizedMedia.clear(); } attachIframeTrigger(ie, sw) { const { actionLogger: al, domScanner: ds } = this.context; if (ie.dataset.dmzHasTrigger === 'true' || ie.dataset.dmzTriggerDismissed === 'true') return; if (ie.nextElementSibling && ie.nextElementSibling.className === 'dmz-iframe-remote-trigger') return; ie.dataset.dmzHasTrigger = 'true'; const p = ie.parentElement; if (!p) return; al.log(`[跨域交互] 为 Iframe 添加智能层级触发器...`, LOG.UI); let raf = null; const od = (o) => { ie.dataset.dmzTriggerDismissed = 'true'; o.remove(); if (raf) cancelAnimationFrame(raf); }, oa = (e, o) => { if (this.isRestoreInProgress) return; al.log(`[跨域交互] 外部触发器被点击...`, LOG.SWITCH); o.style.opacity = '0'; o.style.pointerEvents = 'none'; o.dataset.userHidden = 'true'; if (raf) cancelAnimationFrame(raf); this.isSystemHalted = false; this.setUserIntentToSwitch(); ds.isStopped = false; ds.init(); if (this.lastSuccessfulUrl && this.lastSuccessfulUrl.startsWith('http')) { al.log(`[记忆回溯] 顶层直接复用历史地址进行重播: ${this.lastSuccessfulUrl.slice(-50)}`, LOG.EXEC); this.addTakeoverCandidate({ url: this.lastSuccessfulUrl, sourceName: '跨域遥控触发(顶层记忆)' }); try { sw.postMessage({ type: MSG.CLICK }, '*'); } catch (e) { } return; } al.log(`[跨域交互] 无历史记忆,向 Iframe 发送搜寻指令...`, LOG.COMM); try { sw.postMessage({ type: MSG.CLICK }, '*'); } catch (e) { al.log(`[跨域交互] 指令发送失败: ${e.message}`, LOG.ERROR); } }; const o = this.createTriggerOverlay(ie, oa, od, false), up = () => { if (!ie.isConnected) { o.remove(); return; } const op = o.offsetParent || document.body, ir = ie.getBoundingClientRect(), pr = op.getBoundingClientRect(), ps = window.getComputedStyle(op), bt = parseFloat(ps.borderTopWidth) || 0, bl = parseFloat(ps.borderLeftWidth) || 0; let rt = ir.top - pr.top - bt, rl = ir.left - pr.left - bl; if (op !== document.body && op !== document.documentElement) { rt += op.scrollTop; rl += op.scrollLeft; } o.style.top = `${rt}px`; o.style.left = `${rl}px`; o.style.width = `${ir.width}px`; o.style.height = `${ir.height}px`; const is = window.getComputedStyle(ie), iz = parseInt(is.zIndex); let tz = isNaN(iz) ? 20 : iz + 10; tz = Math.min(tz, 2147483640); o.style.zIndex = tz; if (ir.width < 10 || ir.height < 10) { o.style.opacity = '0'; o.style.pointerEvents = 'none'; } else if (!o.dataset.userHidden) { o.style.opacity = '1'; o.style.pointerEvents = 'auto'; } raf = requestAnimationFrame(up); }; o.dmzUpdatePos = () => { if (raf) cancelAnimationFrame(raf); delete o.dataset.userHidden; o.style.opacity = '1'; o.style.pointerEvents = 'auto'; up(); }; if (ie.nextSibling) p.insertBefore(o, ie.nextSibling); else p.appendChild(o); requestAnimationFrame(up); o.cleanup = () => { if (raf) cancelAnimationFrame(raf); o.remove(); }; } handleRemoteTriggerClick() { const { actionLogger: al, domScanner: ds } = this.context; al.log(`[跨域交互] 收到来自顶层的激活指令,开始执行内部唤醒程序...`, LOG.EXEC); this.isSystemHalted = false; this.setUserIntentToSwitch(); ds.isStopped = false; document.querySelectorAll('.dmz-switch-overlay').forEach((e) => { e.style.display = 'none'; }); scanForFlashvars(this.context); scanForEmbeddedData(this.context); this.neutralizedMedia.forEach(m => { delete m.dataset.dmzSourceLocked; let tu = m.src || m.querySelector('source')?.src || m.dataset.dmzOriginalSrc; if ((!tu || tu.startsWith('blob:')) && this.lastSuccessfulUrl) tu = this.lastSuccessfulUrl; if (tu && tu.startsWith('http')) this.addTakeoverCandidate({ url: tu, sourceName: '跨域遥控触发' }); else { m.dataset.dmzAllowSignalGeneration = 'true'; try { m.currentTime = 0; m.play(); } catch (e) { } setTimeout(() => { delete m.dataset.dmzAllowSignalGeneration; m.pause(); }, 1500); } }); ds.scanPage(); } restorePageToOriginalState() { const { actionLogger: al, domScanner: ds, frameCommunicator: fc, playerManager: pm } = this.context; if (this.isRestoreInProgress) return; this.isRestoreInProgress = true; pm.cleanup(); al.log(`🛡️ [系统熔断] 收到关闭指令 (${IS_TOP_FRAME ? '顶层' : '子框架'}),执行深度清理...`, LOG.LIFE); if (IS_TOP_FRAME) { try { const fs = window.frames; for (let i = 0; i < fs.length; i++) fs[i].postMessage({ type: 'DMZ_RESTORE_COMMAND_V1' }, '*'); if (fs.length > 0) al.log(`已向 ${fs.length} 个子框架发送[全局恢复]指令。`, LOG.COMM); } catch (e) { } } this.isSystemHalted = true; ds.stop(); if (ds.observer) ds.observer.disconnect(); if (ds.heartbeatTimer) clearInterval(ds.heartbeatTimer); if (window.IframeTerminator) try { window.IframeTerminator.stop(); } catch (e) { } else if (IframeTerminator) try { IframeTerminator.stop(); } catch (e) { } this.stopPersistentEnforcer(); document.querySelectorAll('.dmz-switch-overlay').forEach(e => e.remove()); document.querySelectorAll('.dmz-iframe-remote-trigger').forEach(e => { if (e.cleanup) e.cleanup(); else e.remove(); }); const am = this.findAllVideosAndAudioInPage(), ts = new Set([...this.neutralizedMedia, ...am]); ts.forEach(m => { try { if (m.parentElement) { const o = m.parentElement.querySelector('.dmz-switch-overlay'); if (o) o.remove(); } m.dataset.dmzAllowSignalGeneration = 'true'; if (m.hasOwnProperty('play')) delete m.play; if (m.hasOwnProperty('pause')) delete m.pause; try { if (m.play.toString().includes('native code') === false) m.play = HTMLMediaElement.prototype.play; if (m.pause.toString().includes('native code') === false) m.pause = HTMLMediaElement.prototype.pause; } catch (e) { } delete m.dataset.dmzNeutralized; delete m.dataset.dmzOriginalSrc; delete m.dataset.dmzSourceLocked; delete m.dataset.dmzSentinelActive; m.style.visibility = ''; m.style.opacity = ''; m.style.pointerEvents = ''; m.muted = false; if (m.parentElement && m.parentElement.style.position === 'relative') m.parentElement.style.position = ''; } catch (e) { } }); this.neutralizedMedia.clear(); this.activeNeutralizers.forEach(o => o.disconnect()); this.activeNeutralizers.clear(); [NetworkInterceptor, PlayerHooker, DecryptionHooker, JsonInspector, SetAttributeHooker].forEach(m => { if (m && m.isActive) m.disable(this.context); }); if (this.hiddenIframeElement) { this.hiddenIframeElement.style.visibility = this.originalIframeVisibilityStyle || 'visible'; this.hiddenIframeElement.style.pointerEvents = this.originalIframePointerEventsStyle || 'auto'; this.hiddenIframeElement = null; } if (IS_TOP_FRAME) { al.log("✅ 原生环境已恢复 (Top)。", LOG.LIFE); fc.showNotification('沉浸模式已退出,所有框架已恢复原生状态。'); } this.isRestoreInProgress = false; } _cleanupSandbox() { if (this.sandboxReloadTimer) { clearTimeout(this.sandboxReloadTimer); this.sandboxReloadTimer = null; } if (this.iframeSandbox) { this.iframeSandbox.remove(); this.iframeSandbox = null; this.context.actionLogger.log(`[沙箱] iFrame沙箱已清理。`, LOG.IFRAME); } } userIntendsToSwitch = false; userActionTimeout = null; initializeUserIntentObserver() { if (!IS_TOP_FRAME) return; const { playerManager: pm, actionLogger: al } = this.context; document.addEventListener('click', (e) => { if (this.isSystemHalted) return; if (!e.isTrusted) return; let t = e.target; if (!t) return; const p = e.composedPath(), ip = pm.hostElement && p.includes(pm.hostElement), is = !!t.closest(`#${C.SETTINGS}`); if (ip || is) return; const ot = t, iw = t.closest('a, button, [role="button"], li'); if (iw) t = iw; const r = t.getBoundingClientRect(), sa = window.innerWidth * window.innerHeight; if ((r.width * r.height) > (sa * 0.25)) return; const txt = (t.textContent || '').trim(), tag = t.tagName.toUpperCase(), cls = (t.className || '').toString().toLowerCase(), kw = /第|集|话|季|EP\d+|E\d+|下一|上一|重播|Play|播放|刷新|路线|线路|源|高清|极速|PV|CM|Prev|Next|^[<>«»◀▶‹›]$/i; if (txt.length > 0 && txt.length < 12 && kw.test(txt)) { this.setUserIntentToSwitch(); al.log(`👆 [交互] 命中关键词 [${txt}],许可。`, LOG.SWITCH); return; } const isq = Math.abs(r.width - r.height) < 15, ism = r.width < 60 && r.height < 60 && r.width > 20; if ((tag === 'A' || tag === 'BUTTON' || tag === 'LI') && ism && isq) { this.setUserIntentToSwitch(); al.log(`👆 [交互] 命中方形功能按钮 (无文字/图标),许可。`, LOG.SWITCH); return; } const iit = ['SVG', 'I', 'EM', 'PATH', 'RECT'].includes(ot.tagName.toUpperCase()), inc = cls.includes('btn') || cls.includes('play') || cls.includes('prev') || cls.includes('next') || cls.includes('arrow') || cls.includes('icon') || cls.includes('fa-'); if (iit || inc) { if (['A', 'BUTTON', 'LI'].includes(tag) || t.getAttribute('onclick')) { this.setUserIntentToSwitch(); al.log(`👆 [交互] 命中图标/导航类名 [${cls}],许可。`, LOG.SWITCH); return; } } const isn = /^\d{1,3}$/.test(txt); if (isn) { const p = t.closest('[class*="list"], [class*="play"], [class*="episode"], [class*="server"]'); if (p) { this.setUserIntentToSwitch(); al.log(`👆 [交互] 纯数字选集 [${txt}],许可。`, LOG.SWITCH); } } }, true); al.log("高级用户意图监听器已部署 (结构感知版)。", LOG.INIT); } setUserIntentToSwitch() { this.cancelSandboxCountdown("用户交互"); this.context.actionLogger.log("🔄 [用户操作] 检测到点击,强制重置所有决策锁,为新视频让路。", LOG.SWITCH); this.userIntendsToSwitch = true; this.decisionInProgress = false; this.decisionMade = false; this.failedCandidateKeys.clear(); if (this.userActionTimeout) clearTimeout(this.userActionTimeout); this.userActionTimeout = setTimeout(() => { this.userIntendsToSwitch = false; }, 10000); } async _fetchM3u8Text(u, rid) { const { actionLogger: al, diagnosticsTool: dt } = this.context; al.log(`[ID:${rid}] 获取M3U8清单文本: ${u}`, LOG.CORE); const c = new AbortController(), tid = setTimeout(() => c.abort(), 3000); try { const r = await fetch(u, { method: 'GET', headers: {}, signal: c.signal }); clearTimeout(tid); if (this.currentRequestId !== rid) throw new Error('Request outdated'); if (r.ok) return await r.text(); else { if (dt.playbackHealth.manifest.status === 'pending') dt.playbackHealth.manifest = { status: 'error', code: r.status }; throw new Error(`获取 M3U8 失败: ${r.status} ${r.statusText}`); } } catch (e) { clearTimeout(tid); if (this.currentRequestId !== rid) return new Promise(() => { }); if (e.name === 'AbortError') throw new Error('获取 M3U8 失败: Timeout'); else { if (dt.playbackHealth.manifest.status === 'pending') dt.playbackHealth.manifest = { status: 'error', code: 'Network Error' }; throw new Error(`获取 M3U8 失败: ${e.message || 'Network Error'}`); } } } _rebuildM3u8FromGroups(ol, fg, o = {}) { const { filterHeaderDiscontinuity: fhd = false } = o, hi = ol.findIndex(l => l.startsWith('#EXTINF')); let hl = hi > -1 ? ol.slice(0, hi) : []; if (fhd) hl = hl.filter(l => !l.trim().startsWith('#EXT-X-DISCONTINUITY')); let fl = [...hl]; fg.forEach((g, i) => { fl.push(...g); if (i < fg.length - 1) fl.push('#EXT-X-DISCONTINUITY'); }); const ep = ol.some(l => l.includes('#EXT-X-ENDLIST')); if (ep && !fl.some(l => l.includes('#EXT-X-ENDLIST'))) fl.push('#EXT-X-ENDLIST'); return fl.filter(Boolean); } _sliceAdSegmentsByKeywords(ls, ks) { const { actionLogger: al, diagnosticsTool: dt } = this.context; al.log(`执行「URL特征」净化,关键词: [${ks.join(', ')}]`, LOG.SLICE); const am = new RegExp(ks.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')), sgs = []; let cg = []; ls.forEach(l => { if (l.trim().startsWith('#EXT-X-DISCONTINUITY')) { if (cg.length > 0) sgs.push(cg); cg = []; } else cg.push(l); }); if (cg.length > 0) sgs.push(cg); if (sgs.length === 0) return ls; let rg = 0, rs = 0; const fgs = []; let otd = 0; sgs.forEach(g => { const ia = g.some(l => am.test(l)), gd = g.filter(l => l.startsWith('#EXTINF:')).reduce((s, l) => { const d = parseFloat(l.split(':')[1]); return s + (isNaN(d) ? 0 : d); }, 0); if (ia) { rg++; rs += g.filter(l => l.trim().endsWith('.ts') || l.trim().includes('.jpeg')).length; dt.slicingReport.slicedDuration += gd; if (gd > 0) dt.slicingReport.slicedTimeRanges.push({ start: otd, end: otd + gd }); } else fgs.push(g); otd += gd; }); if (rg === 0) return ls; dt.slicingReport.slicedGroups += rg; dt.slicingReport.slicedSegments += rs; al.log(`「URL特征」净化完成,移除 ${rg} 组共 ${rs} 个广告分片。`, LOG.SLICE); const fl = this._rebuildM3u8FromGroups(ls, fgs, { filterHeaderDiscontinuity: true }); if (this._hasMediaSegments(fl)) return fl; else { al.log(`[安全锁] “特征锁定”净化后无任何可播放内容!已还原为原始列表。`, LOG.SLICE); dt.slicingReport.slicedGroups -= rg; dt.slicingReport.slicedSegments -= rs; return ls; } } _runFalconV3Engine(ls, bu) { const { actionLogger: al, diagnosticsTool: dt } = this.context; al.log("「行为清扫」:启动,扫描典型短广告分组...", LOG.SLICE); const sgs = []; let cg = []; for (const l of ls) { if (l.trim().startsWith('#EXT-X-DISCONTINUITY')) { if (cg.length > 0) sgs.push(cg); cg = []; } cg.push(l); } if (cg.length > 0) sgs.push(cg); if (sgs.length < 2) { al.log(`「行为清扫」视频分组不足(少于2个),不执行行为净化。`, LOG.SLICE); return { wasSliced: false, content: ls }; } const cgs = sgs.map(gl => { const ds = gl.filter(l => l.startsWith('#EXTINF:')).map(l => parseFloat(l.split(':')[1])), sc = ds.length, td = ds.reduce((a, b) => a + b, 0), iac = sc > 0 && sc <= 6 && td <= 30; return { lines: gl, isAdCandidate: iac, segmentCount: sc, totalDuration: td }; }); const ac = cgs.filter(g => g.isAdCandidate).length, ar = ac / cgs.length; if (ar > 0.8) { al.log(`「行为清扫-结构分析」 ⚠️检测到 ${Math.round(ar * 100)}% 的分组为短分组,疑似正片被切割。为防止误杀,已跳过本次「行为清扫」。`, LOG.SLICE); return { wasSliced: false, content: ls }; } const cc = cgs.filter(g => !g.isAdCandidate).length; if (cc === 0) { al.log(`「行为清扫-安全锁」 未识别到明确的“主体内容”分组,判定为短视频。为防止误杀,已中止净化。`, LOG.SLICE); return { wasSliced: false, content: ls }; } let rg = 0, rs = 0; const fgs = []; let otd = 0; cgs.forEach(g => { if (g.isAdCandidate) { rg++; rs += g.segmentCount; dt.slicingReport.slicedDuration += g.totalDuration; const f = `<高置信度短分组> (分片数:${g.segmentCount}, 时长:${g.totalDuration.toFixed(1)}s)`; dt.slicingReport.foundFeatures.get('BEHAVIOR_MODEL').add(f); al.log(`「行为清扫」战果:移除广告组 (${g.segmentCount}个分片, ${g.totalDuration.toFixed(1)}s)。`, LOG.SLICE); if (g.totalDuration > 0) dt.slicingReport.slicedTimeRanges.push({ start: otd, end: otd + g.totalDuration }); } else fgs.push(g.lines); otd += g.totalDuration; }); if (rg === 0) { al.log(`「行为清扫」未发现符合条件的广告。`, LOG.SLICE); return { wasSliced: false, content: ls }; } dt.slicingReport.activatedEngines.add('BEHAVIOR_MODEL'); dt.slicingReport.slicedGroups += rg; dt.slicingReport.slicedSegments += rs; al.log(`净化报告:通过「行为清扫」移除 ${rg} 组共 ${rs} 个广告分片。`, LOG.SLICE); const fl = this._rebuildM3u8FromGroups(ls, fgs); if (this._hasMediaSegments(fl)) return { wasSliced: true, content: fl }; else { al.log(`[安全锁] “行为清扫”净化后无任何可播放内容!已还原为原始列表。`, LOG.SLICE); dt.slicingReport.slicedGroups -= rg; dt.slicingReport.slicedSegments -= rs; return { wasSliced: false, content: ls }; } } _hasMediaSegments(ls) { const mp = /\.(ts|mp4|mkv|webm|flv|jpeg|jpg)(\?.*)?$/i; return ls.some(l => !l.startsWith('#') && mp.test(l.trim())); } _resolveRelativeUrls(ls, bu) { if (bu.startsWith('data:')) { this.context.actionLogger.log(`检测到 Data URI,跳过URL解析,保留相对路径。`, LOG.URL); return ls; } this.context.actionLogger.log(`解析M3U8中的相对URL,基础URL: ${bu}`, LOG.URL); const ru = (r, b) => new URL(r, b).href, ia = (u) => /^(https?:)?\/\//.test(u); return ls.map(l => { const t = l.trim(); if (t.length === 0 || !t.includes('URI=') && (t.startsWith('#EXT') && !t.startsWith('#EXT-X-KEY') && !t.startsWith('#EXT-X-MEDIA'))) return l; try { if (!t.startsWith('#')) { if ((/\.(ts|mp4|mkv|webm|flv|m3u8?|jpeg|m4s)(\?.*)?$/i).test(t) && !ia(t)) return ru(t, bu); } else if (t.startsWith('#EXT-X-KEY') || t.startsWith('#EXT-X-MEDIA') || t.startsWith('#EXT-X-MAP')) { const um = t.match(/URI="([^"]+)"/), u = um?.[1]; if (u && !ia(u)) return t.replace(u, ru(u, bu)); } } catch (e) { } return l; }); } async processM3U8(it, iu, rid) { const { actionLogger: al, settingsManager: sm, diagnosticsTool: dt, playerManager: pm } = this.context; al.log(`指令分发:将清单送往[广告过滤]模块进行净化。`, LOG.CORE); let ct = it, bu; try { bu = new URL(iu).href; } catch (e) { bu = new URL(iu, window.location.href).href; al.log(`初始URL为相对路径,已拼接为绝对路径: ${bu}`, LOG.URL); } let cu = bu; try { for (let i = 0; i < C.MAX_DEPTH; i++) { if (this.currentRequestId !== rid) throw new Error('Request outdated'); const ls = ct.split('\n'), im = ls.some(l => l.startsWith('#EXT-X-STREAM-INF')); if (im) al.log(`发现 Master Playlist,开始解析可用清晰度...`, LOG.STREAM); const hm = ls.some(l => l.startsWith('#EXTINF')); let nul = null; if ((im && !hm) || (ls.length < 5 && !hm)) { const qu = ls.filter(l => l.trim() && !l.startsWith('#') && (l.endsWith('.m3u8') || l.endsWith('.m3u'))); if (qu.length > 0) { const qr = { "2160p": 6, "1080p": 5, "720p": 4, "480p": 3, "360p": 2, "240p": 1 }; let bq = -1, bu = qu[qu.length - 1]; qu.forEach(u => { for (const q in qr) { if (u.toLowerCase().includes(q) && qr[q] > bq) { bq = qr[q]; bu = u; } } }); al.log(`解析完成, 共 ${qu.length} 个流。自动选择最高清晰度: ${Object.keys(qr).find(k => qr[k] === bq) || '未知'}`, LOG.STREAM); nul = bu; } } if (nul) { cu = new URL(nul, cu).href; ct = await this._fetchM3u8Text(cu, rid); continue; } break; } if (this.currentRequestId !== rid) throw new Error('Request outdated'); pm.lastM3u8Content = ct; let pl = ct.split('\n'); if (sm.config.isSmartSlicingEnabled) { const hd = ct.includes('#EXT-X-DISCONTINUITY'); if (hd) { const uk = dt.analyzeUrlPatterns(ct, cu); if (uk?.length > 0) { al.log(`⚖️决策:「URL特征」发现<高置信度>关键词,执行精准净化。`, LOG.SLICE); pl = this._sliceAdSegmentsByKeywords(pl, uk); } else al.log(`⚖️决策:「URL特征」未发现,转交「行为清扫」。`, LOG.SLICE); } else al.log(`⚖️决策:M3U8结构简单(无分组),跳过路径分析以防误判,直接进入行为分析。`, LOG.SLICE); let fr = this._runFalconV3Engine(pl, cu); if (fr.wasSliced) pl = fr.content; } else al.log("智能净化已禁用,跳过处理", LOG.SLICE); const fl = this._resolveRelativeUrls(pl, cu); let r = fl.join('\n'); if (!r.trim().startsWith('#EXTM3U')) r = '#EXTM3U\n' + r; al.log(`[ID:${rid}] M3U8处理完成: 最终URL为 ${cu}`, LOG.CORE); return { original: ct, processed: r, finalUrl: cu }; } catch (e) { al.log(`[ID:${rid}] 处理M3U8时出错: ${iu}, 错误: ${e.message}`, LOG.ERROR); throw e; } } _getNormalizationKey(u) { if (typeof u !== 'string') return null; if (u.startsWith('blob:')) return u; try { const o = new URL(u); if (o.hostname.includes('googlevideo.com')) return u.split('?')[0]; return `${o.protocol}//${o.hostname}${o.pathname}`; } catch (e) { return u; } } _extractUrlFromParameter(us) { try { const o = new URL(us), ps = ['url', 'src', 'vid', 'play_url']; for (const p of ps) { if (o.searchParams.has(p)) { const nu = o.searchParams.get(p); if (nu && (nu.startsWith('http') || nu.startsWith('/'))) { this.context.actionLogger.log(`在URL参数'${p}'中提取到真实视频地址。`, LOG.URL); return nu; } } } } catch (e) { } return us; } async addTakeoverCandidate(ci) { if (this.isSystemHalted) return; ci.url = this._extractUrlFromParameter(ci.url); const { actionLogger: al, playerManager: pm, frameCommunicator: fc } = this.context; if (this.staleVideoUrl && ci.url && (ci.url === this.staleVideoUrl || ci.url.includes(this.staleVideoUrl))) { if (!ci.sourceName.includes('触发器')) { al.log(`👻 [幽灵过滤] 拦截到上一页面的尸体信号: ${ci.url.slice(-30)}`, LOG.WARN); return; } } if (ci.sourceName && ci.sourceName.includes('触发器')) { al.log(`👆 收到 [${ci.sourceName}] 信号,强制确认为用户交互意图。`, LOG.SWITCH); this.setUserIntentToSwitch(); } if (ci.url && ci.url.startsWith('blob:')) { if (ci.sourceName.includes('网络拦截')) al.log(`🚧 [门禁] ✅ 放行网络拦截 Blob (高置信度) | 来源: ${ci.sourceName}`, LOG.ATT); else if (ci.sourceName.includes('扫描') || ci.sourceName.includes('DOM')) { al.log(`🚧 [门禁] ⛔️ 拦截 DOM 扫描 Blob (疑似预览/干扰) | 来源: ${ci.sourceName}`, LOG.ATT); return; } else if (this.lastSuccessfulUrl) al.log(`⚠️ 检测到 blob URL 变动,尝试使用核心档案中的真实地址。`, LOG.WARN); else al.log(`🚧 [门禁] ⚠️ 尝试放行 Blob URL (未知来源) | 来源: ${ci.sourceName}`, LOG.ATT); } if (pm.isPlayerActiveOrInitializing && this.userIntendsToSwitch) { al.log(`📳[最高指令] 用户换集意图触发,强制刷新所有决策!`, LOG.SWITCH); pm.cleanup(); this.decisionMade = false; this.decisionInProgress = false; this.takeoverCandidates.clear(); this.lastCandidatesBackup.clear(); this.userIntendsToSwitch = false; clearTimeout(this.backupBufferTimer); this.isBufferingBackupCandidates = false; al.log(`[最高指令] 清理完成,系统已重置。`, LOG.SWITCH); } if (this.isBufferingBackupCandidates) { al.log(`[并行备份] 窗口期内,备用信号 [${ci.sourceName}] 已存档。`, LOG.INFO); const k = this._getNormalizationKey(ci.url); if (k && !this.lastCandidatesBackup.has(k)) this.lastCandidatesBackup.set(k, { ...ci, sources: new Set([ci.sourceName]) }); return; } if ((this.decisionInProgress || pm.isPlayerActiveOrInitializing) && !this.userIntendsToSwitch) { if (ci.mediaElement && !isPreRollAdByPathFeatures(ci.url, this.context)) this.neutralizeOriginalPlayer(ci.mediaElement); if (ci.url && !ci.url.startsWith('blob:')) { this.passiveCandidates.set(ci.url, ci); if (this.passiveCandidates.size > 15) { const fk = this.passiveCandidates.keys().next().value; this.passiveCandidates.delete(fk); } } return; } if (!IS_TOP_FRAME) { al.log(`[iFrame勘查员] 发现目标,打包情报上报...`, LOG.COMM); const pl = { url: ci.url, sourceName: ci.sourceName, m3u8Text: ci.m3u8Text || null, survey: ci.survey, mediaElement: null }; fc.postToTopFrame({ type: MSG.RAW, payload: pl }); return; } this.decisionInProgress = true; al.log(`[极速决策] 信号 [${ci.sourceName}] 进入决策流程,系统已上锁。`, LOG.INFO); try { if (!this.context.behavioralFilter) this.context.behavioralFilter = new BehavioralFilter(this.context); const ar = await this.context.behavioralFilter.analyze(ci); if (!ar.isLegitimate) { al.log(`[极速决策] 信号 [${ci.sourceName}] 未通过门禁,系统解锁。`, LOG.INFO); this.decisionInProgress = false; return; } this.cancelSandboxCountdown(`捕获到有效信号: ${ci.sourceName}`); al.log(`[极速决策] ✅ 合格信号已确认 (来源: ${ci.sourceName})。立即执行接管!`, LOG.SUCC); al.log(`[并行备份] ⏱️ 3秒备份窗口已在后台开启...`, LOG.INFO); this.isBufferingBackupCandidates = true; this.backupBufferTimer = setTimeout(() => { this.isBufferingBackupCandidates = false; al.log(`[并行备份] ⏱️ 备份窗口关闭。`, LOG.INFO); }, 3000); await this._executeTakeover(ci); } catch (e) { al.log(`[极速决策] 接管执行失败: ${e.message}`, LOG.FAIL); this.decisionInProgress = false; this.reportPlaybackFailure(); } } async _executeTakeover(d) { const { coreLogic: cl } = this.context; cl.findAllVideosAndAudioInPage().forEach(m => { const o = cl.neutralizeOriginalPlayer(m); if (o) cl.activeNeutralizers.add(o); }); if (!d.sources && d.sourceName) d.sources = new Set([d.sourceName]); this.lastCandidatesBackup = new Map(this.takeoverCandidates); const { actionLogger: al, playerManager: pm, diagnosticsTool: dt } = this.context; if (d.url && !d.url.startsWith('blob:')) this.lastSuccessfulUrl = d.url; al.log(`⚖️「DIADE」决策锁定: [${d.url.slice(-50)}] (来源: ${Array.from(d.sources).join(' + ')})`, LOG.SUCC); const { url: u, m3u8Text: mt } = d, rid = ++this.currentRequestId; dt.captureTakeoverEvidence(d); try { dt.resetPlaybackHealth(); dt.resetSlicingReport(); const im = Utils.isM3U8(u) || (mt && mt.includes('#EXTM3U')), vt = im ? 'm3u8' : 'normal'; let vd = u; if (im) { const tp = mt || await this._fetchM3u8Text(u, rid); if (this.currentRequestId !== rid) throw new Error('Request outdated'); const pr = await this.processM3U8(tp, u, rid); if (this.currentRequestId !== rid) throw new Error('Request outdated'); if (!pr?.processed) throw new Error("M3U8处理后内容为空"); vd = pr; } else pm.currentVideoFormat = Utils.getVideoFormat(u); await this.sendPlayCommand(vt, vd); } catch (e) { if (e.message !== 'Request outdated') { al.log(`「DIADE」[ID:${rid}] 处理视频源失败: ${e.message}`, LOG.FAIL); this.reportPlaybackFailure(); } } } async sendPlayCommand(t, c) { const { playerManager: pm, diagnosticsTool: dt, frameCommunicator: fc, actionLogger: al } = this.context, is = new URLSearchParams(window.location.search).has('dmz_sandbox'); if (is) { const ci = { url: (t === 'm3u8') ? c.finalUrl : c, sourceName: SRC.FRAME, m3u8Text: (t === 'm3u8') ? c.original : null }; fc.postToTopFrame({ type: MSG.SANDBOX, candidateInfo: ci }); return; } if (pm.isPlayerActiveOrInitializing) return; this._cleanupSandbox(); if (!IS_TOP_FRAME) { this.lastPostedUrl = (t === 'm3u8') ? c.finalUrl : c; const il = al.getLogs().filter(l => l.type === LOG.SLICE), m = { type: MSG.M3U8, action: t === 'm3u8' ? 'PLAY_M3U8' : 'PLAY_NORMAL_VIDEO', content: (t === 'm3u8') ? c.processed : c, originalContent: (t === 'm3u8') ? c.original : null, finalUrl: this.lastPostedUrl, videoFormat: pm.currentVideoFormat, playbackHealth: dt.playbackHealth, slicingReport: dt.slicingReport, iframeLogs: il }; this.findAllVideosAndAudioInPage().forEach(m => this.neutralizeOriginalPlayer(m)); fc.postToTopFrame(m); } else await pm.play(c); } findAllVideosAndAudioInPage() { const m = [], v = new Set(); function f(n) { if (!n || v.has(n)) return; v.add(n); try { n.querySelectorAll('video, audio').forEach(e => m.push(e)); n.querySelectorAll('*').forEach(e => e.shadowRoot && f(e.shadowRoot)); } catch (e) { } } if (document.body) f(document.body); return m; } neutralizeOriginalPlayer(m, is = 'active') { const { actionLogger: al, coreLogic: cl } = this.context; if (!m || m.id === C.PLAYER || m.dataset.dmzDismissed === 'true') return null; if (m.loop && m.muted && !m.controls) return null; if (m.dataset.dmzNeutralized === 'true') { if (!m.paused && m.dataset.dmzAllowSignalGeneration !== 'true') m.pause(); if (!m.muted) m.muted = true; return null; } if (m.dataset.dmzNeutralized === 'passive' && is === 'passive') return null; if (m.dataset.dmzNeutralized === 'passive' && is === 'active') { al.log(`[智能穿透] 监测到视频源转正,立即激活压制器!`, LOG.NEUT); m.dataset.dmzNeutralized = 'true'; if (m.dmzOverlay) m.dmzOverlay.style.display = 'flex'; m.pause(); m.muted = true; return null; } const ip = is === 'passive', giz = (e) => { let cr = e.parentElement; while (cr && cr.tagName !== 'BODY' && window.getComputedStyle(cr).position === 'static') cr = cr.parentElement; cr = cr || e.parentElement; if (!cr) return 10; let mz = 1, rs = window.getComputedStyle(cr), rz = parseInt(rs.zIndex, 10); if (!isNaN(rz)) mz = Math.max(mz, rz); cr.querySelectorAll('*').forEach(d => { const s = window.getComputedStyle(d), cz = parseInt(s.zIndex, 10); if (!isNaN(cz)) mz = Math.max(mz, cz); }); return mz + 1; }, tag = m.tagName; al.log(`开始改造原生 ${tag} 为切换触发器...`, LOG.NEUT); const os = m.src || m.querySelector('source[src]')?.src; if (os) m.dataset.dmzOriginalSrc = os; m.dataset.dmzNeutralized = ip ? 'passive' : 'true'; m.dmzOverlay = null; if (!ip) { m.pause(); m.muted = true; m.volume = 0; m.autoplay = false; m.loop = false; m.removeAttribute('autoplay'); } const pp = (e) => { if (m.dataset.dmzNeutralized === 'passive' || m.dataset.dmzAllowSignalGeneration === 'true' || !e.isTrusted) return; m.pause(); m.muted = true; Utils.stopEvent(e); e.stopImmediatePropagation(); }; m.addEventListener('play', pp, true); m.addEventListener('playing', pp, true); m.addEventListener('canplay', () => { if (m.dataset.dmzNeutralized === 'passive' || m.dataset.dmzAllowSignalGeneration === 'true') return; m.pause(); m.muted = true; }, { once: false, passive: true }); m.style.removeProperty('visibility'); const p = m.parentElement; if (p && window.getComputedStyle(p).position === 'static') p.style.position = 'relative'; const od = (o) => { o.remove(); cl.neutralizedMedia.delete(m); m.dataset.dmzDismissed = 'true'; delete m.dataset.dmzNeutralized; delete m.dataset.dmzSourceLocked; if (m.hasOwnProperty('play')) delete m.play; if (m.hasOwnProperty('pause')) delete m.pause; m.style.visibility = ''; m.style.opacity = ''; m.style.pointerEvents = ''; m.muted = false; m.controls = true; }, oa = (e, o) => { if (cl.isRestoreInProgress) return; if (o.intersectionObserver) { o.intersectionObserver.disconnect(); o.intersectionObserver = null; } cl.setUserIntentToSwitch(); al.log(`播放器触发器被点击,系统已重置,准备捕获视频流...`, LOG.SWITCH); if (IS_TOP_FRAME) { scanForFlashvars(cl.context); scanForEmbeddedData(cl.context); } let du = m.src || m.querySelector('source')?.src || m.dataset.dmzOriginalSrc; if (du && du.startsWith('http')) { al.log(`[直接激活] 发现当前元素已有有效直链,立即播放: ${du.slice(-30)}`, LOG.EXEC); cl.addTakeoverCandidate({ url: du, sourceName: '原生播放器触发器(直连)' }); return; } if (cl.passiveCandidates.size > 0) { const cs = Array.from(cl.passiveCandidates.values()).reverse(), bm = cs.find(c => (c.m3u8Text || Utils.isM3U8(c.url))) || cs[0]; if (bm) { al.log(`[智能穿透] ⚡️命中后台缓存的潜伏信号,立即接管: ${bm.url.slice(-50)}`, LOG.EXEC); cl.passiveCandidates.clear(); cl.addTakeoverCandidate(bm); return; } } al.log(`[交互] 触发器点击:源无效(Blob/空),强制启动沙箱重载以获取新签名...`, LOG.EXEC); cl.hasTriggeredSandbox = false; if (cl.iframeSandbox) cl._cleanupSandbox(); cl.reloadInSandbox(); }, o = this.createTriggerOverlay(m, oa, od, ip); o.style.zIndex = giz(m); if (!p.querySelector('.dmz-switch-overlay')) { p.appendChild(o); m.dmzOverlay = o; const oo = { root: null, rootMargin: "0px", threshold: 1.0 }, oc = (es) => { if (m.dataset.dmzNeutralized === 'passive') return; const e = es[0], ifo = e.isIntersecting && e.boundingClientRect.top >= 0; o.style.opacity = ifo ? '1' : '0'; o.style.pointerEvents = ifo ? 'auto' : 'none'; }, io = new IntersectionObserver(oc, oo); io.observe(m); o.intersectionObserver = io; } if (!ip) { try { Object.defineProperties(m, { play: { value: () => { if (m.dataset.dmzAllowSignalGeneration === 'true') return HTMLElement.prototype.play.call(m); return Promise.resolve(); }, configurable: true }, pause: { value: () => { if (m.dataset.dmzAllowSignalGeneration === 'true') return HTMLElement.prototype.pause.call(m); }, configurable: true }, }); } catch (e) { } } const ao = new MutationObserver(() => { const ns = m.src || m.currentSrc; if (ns && ns !== m.dataset.dmzOriginalSrc) { m.dataset.dmzOriginalSrc = ns; if (m.dataset.dmzNeutralized === 'passive') { if (!isPreRollAdByPathFeatures(ns, cl.context)) cl.neutralizeOriginalPlayer(m, 'active'); return; } if (m.dataset.dmzAllowSignalGeneration !== 'true') { m.pause(); m.muted = true; } } }); ao.observe(m, { attributes: true, attributeFilter: ['src'] }); if (!ip) cl.startPersistentEnforcer(); return ao; } _getBestCandidate(cs) { return cs.sort((a, b) => { const au = a.url, bu = b.url, ah = au.includes('?sign=') || au.includes('&t='), bh = bu.includes('?sign=') || bu.includes('&t='); if (ah && !bh) return -1; if (!ah && bh) return 1; const aa = au.startsWith('http'), ba = bu.startsWith('http'); if (aa && !ba) return -1; if (!aa && ba) return 1; return bu.length - au.length; })[0]; } reportPlaybackFailure(fc = {}) { const { actionLogger: al, playerManager: pm, frameCommunicator: fcm } = this.context, fu = fc.failedUrl || pm.currentVideoUrl, r = fc.reason || 'Playback Error', fk = this._getNormalizationKey(fu); if (!this.failedCandidateKeys) this.failedCandidateKeys = new Set(); if (fk) { this.failedCandidateKeys.add(fk); al.log(`[FTR] 信号源 (${fu ? fu.slice(-50) : '未知'}) 因 (${r}) 已被加入会话黑名单。`, LOG.EXEC); } if (pm.isPlayerActiveOrInitializing) pm.cleanup(); if (this.backupBufferTimer) { clearTimeout(this.backupBufferTimer); this.backupBufferTimer = null; } this.isBufferingBackupCandidates = false; this.decisionMade = false; this.decisionInProgress = false; al.log(`[FTR] 系统决策锁已解除,恢复监听状态。`, LOG.EXEC); const ak = new Map([...this.takeoverCandidates, ...this.bufferedBackupCandidates, ...this.lastCandidatesBackup]), vc = new Map(); for (const [k, c] of ak.entries()) { if (!this.failedCandidateKeys.has(k)) vc.set(k, c); } this.takeoverCandidates.clear(); this.bufferedBackupCandidates.clear(); this.lastCandidatesBackup.clear(); if (vc.size > 0) { al.log(`[FTR] 在备用信号中发现 ${vc.size} 个新选项,立即重新决策...`, LOG.EXEC); const ba = this._getBestCandidate(Array.from(vc.values())); if (ba) this._executeTakeover(ba); } else if (fcm.pendingMainPlayerSource) { al.log(`[FTR] 启用候补 Iframe 信号,尝试激活...`, LOG.EXEC); const { source: s, origin: o, frameElement: fe } = fcm.pendingMainPlayerSource; fcm.mainPlayerFrameSource = s; fcm.mainPlayerFrameOrigin = o; fcm.mainPlayerIframeElement = fe; s.postMessage({ type: MSG.AUTOPLAY }, o); fcm.pendingMainPlayerSource = null; setTimeout(() => { if (!this.context.playerManager.isPlayerActiveOrInitializing) { this.context.actionLogger.log(`[FTR] 候补 Iframe 激活超时(未检测到播放),强制执行沙箱重载。`, LOG.WARN); this.reloadInSandbox(); } }, 3000); } else { if (!this.hasTriggeredSandbox && this._isLikelyVideoPage()) { this.context.actionLogger.log(`[FTR] 所有已知信号均失效,尝试最后手段:沙箱重载。`, LOG.EXEC); this.reloadInSandbox(); } else { this.context.actionLogger.log(`[FTR] 无可用信号且沙箱已使用过(或非视频页),停止尝试。`, LOG.EXEC); } } } } class BehavioralFilter { constructor(c) { this.context = c; } _isSameAsPageUrl(u) { if (!u || typeof u !== 'string') return false; try { const cp = window.location.href.replace(/^(https?:\/\/)?(www\.)?/, '').replace(/\/$/, ''), cc = u.replace(/^(https?:\/\/)?(www\.)?/, '').replace(/\/$/, ''); if (cp.includes('view_video.php')) return cp === cc; if (cc.includes(cp)) return true; } catch (e) { return false; } return false; } async analyze(c) { const { actionLogger: al } = this.context, { url: u, sourceName: sn, m3u8Text: mt } = c; if (isPreRollAdByPathFeatures(u, this.context, c)) return { isLegitimate: false }; const ip = /JSON|DOM|ATTR|扫描|嵌入式|网络|Net/.test(sn), p = window.location.pathname.toLowerCase(), ih = p.length < 2 || /^\/(index|home|default|main)(\.(html|php|jsp|asp|aspx))?\/?$/i.test(p), is = /(^|\/)(s|search|query|results?|so)($|\/|\.|_)/i.test(p), ia = /(^|\/)(auth|login|signin|signup|register|account|dashboard|console|member|profile|settings|my|user)($|\/|\.|_)/i.test(p), il = /(^|\/)(type|vodtype|list|category|tag|channel|column)($|\/|\.|_)/i.test(p); if ((ih || is || ia || il) && ip) { if (/(play|video|watch|detail|view)/i.test(p)) al.log(`🚧[门禁] ⚠️ 疑似列表页但包含播放特征 [${p}],豁免放行。`, LOG.ATT); else { al.log(`🚧[门禁] ⛔️ 拦截 (页面场景自动推荐,防止误扰) | 来源: ${sn}`, LOG.ATT); return { isLegitimate: false }; } } const im = Utils.isM3U8(u) || (mt && mt.includes('#EXTM3U')); if (im) { al.log(`🚧[门禁] ✅ 放行 (高优先级M3U8信号) | 来源: ${sn}`, LOG.ATT); return { isLegitimate: true }; } if (!u.startsWith('http') && !u.startsWith('//') && !u.startsWith('/') && !u.startsWith('blob:')) { al.log(`🚧[门禁] ⛔️ 拦截 (无效的URL片段) | 来源: ${sn} | URL: ${u.slice(0, 80)}`, LOG.ATT); return { isLegitimate: false }; } if (this._isSameAsPageUrl(u)) { al.log(`🚧[门禁] ⛔️ 拦截 (无效的页面URL) | 来源: ${sn}`, LOG.ATT); return { isLegitimate: false }; } if (u.includes('/m3u8/') && !Utils.isM3U8(u)) { al.log(`🚧[门禁] ⛔️ 拦截 (M3U8路径下的非M3U8文件,疑似诱饵) | 来源: ${sn}`, LOG.ATT); return { isLegitimate: false }; } al.log(`🚧[门禁] ✅ 放行 (默认规则) | 来源: ${sn}`, LOG.ATT); return { isLegitimate: true }; } } class SPANavigator { constructor(c) { this.context = c; this.lastUrl = window.location.href; } init() { const ps = history.pushState, rs = history.replaceState; history.pushState = (...a) => { ps.apply(history, a); this.onUrlChange(); }; history.replaceState = (...a) => { rs.apply(history, a); this.onUrlChange(); }; _el(window, 'popstate', () => this.onUrlChange()); } onUrlChange() { if (this.context.coreLogic.isSystemHalted) return; requestAnimationFrame(() => { if (window.location.href.split('#')[0] === this.lastUrl.split('#')[0]) return; const { actionLogger: al, playerManager: pm, coreLogic: cl, domScanner: ds } = this.context; al.log(`URL 变化 (SPA): ${this.lastUrl} -> ${window.location.href}`, LOG.NAV); this.lastUrl = window.location.href; if (cl.lastSuccessfulUrl) { cl.staleVideoUrl = cl.lastSuccessfulUrl; al.log(`[NAV] 已标记旧视频为尸体信号: ${cl.staleVideoUrl.slice(-30)}`, LOG.NAV); } cl.lastSuccessfulUrl = null; pm.cleanup(); cl.activeNeutralizers.forEach(o => o.disconnect()); const sc = new Map(), sp = new Map(), kf = (sm, tm) => { sm.forEach((c, k) => { if (!cl.staleVideoUrl || !c.url.includes(cl.staleVideoUrl.split('?')[0])) tm.set(k, c); }); }; kf(cl.takeoverCandidates, sc); kf(cl.passiveCandidates, sp); if (sc.size > 0 || sp.size > 0) al.log(`[NAV] ♻️ 跨页面保留了 ${sc.size + sp.size} 个潜在的新视频信号。`, LOG.NAV); Object.assign(cl, { decisionMade: false, decisionInProgress: false, takeoverCandidates: sc, lastCandidatesBackup: new Map(), currentRequestId: 0, navigationRequestId: (cl.navigationRequestId || 0) + 1, activeNeutralizers: new Set(), backupBufferTimer: (cl.backupBufferTimer && clearTimeout(cl.backupBufferTimer), null), isBufferingBackupCandidates: false, bufferedBackupCandidates: new Map(), passiveCandidates: sp, hasTriggeredSandbox: false }); ds.stop(); ds.isStopped = false; ds.init(); al.log(`[NAV] 系统大清洗完成,开始监听新信号 (旧信号将被拦截)。`, LOG.NAV); }); } } function isPreRollAdByPathFeatures(us, c, cd = {}) { if (typeof us !== 'string' || !us) return false; const p = window.location.pathname.toLowerCase(), ia = /(^|\/)(auth|login|signin|signup|register|account|dashboard|console|member|profile|settings|my|user)($|\/|\.|_)/i.test(p); if (ia && !/(play|video|watch|detail|view)/i.test(p)) { c.actionLogger.log(`[路径特征分析] 命中页面场景(登录/后台)规则,判定为非目标信号。URL: ${us.slice(-50)}`, LOG.SCAN_W); return true; } const ST = 25, HSR = [{ p: /doubleclick\.net|googlesyndication\.com|adservice\.google/i, r: 'Google Ad Service' }, { p: /vp\.externulls\.com/i, r: 'Known Ad Service' }], R = { K: [{ p: /(preview|trailer|teaser|snippet|sample|intro|opening|ending|partner|advert|background|banner|cover|thumb|poster|sprite|animation|loop|web_preview|short_video)/i, s: 50 }, { p: /(_|\-)(pv|ad|short)(_|\-|$|\.)/i, s: 40 }, { p: /(demo|try)=\d+/i, s: 40 }, { p: /(limit|start|end)=(?!1\b)\d+/i, s: 40 }, { p: /\/ads?([\/\.&\?#]|$)/i, s: 50 }, { p: /advert|preroll|promo|sponsor|vast|vpaid/i, s: 40 }, { p: /\.gif|gif\./i, s: 60 }], Q: [{ p: /^ad_|^utm_|^gclid$/i, s: 30 }, { p: /campaign|creative|impression/i, s: 20 }, { p: /watermark|logo/i, s: 15 }], S: { pct: 8, pcs: 10, lst: 50, lss: 5, xh: { p: /\/(\d+)x(\d+)\./, ma: 640 * 360, s: 30 }, fl: { p: /_(\d{1,3})\.(mp4|ts|flv)/i, mh: 240, s: 30 }, el: { p: /(_|\-)(360p|480p|540p|sd)(_|\-|\.|$)/i, s: 25 } } }, its = cd.iframeOrigin && cd.iframeOrigin.includes('playkrx18.site'); let s = 0, rs = []; R.K.forEach(r => { if (r.p.test(us)) { s += r.s; rs.push(`Keyword[${r.p.source}]: +${r.s}`); } }); if (s >= ST) { c.actionLogger.log(`[路径特征分析] URL判定为广告 (分数: ${s}/${ST}). 原因: ${rs.join(', ')}. URL: ${us.slice(-80)}`, LOG.SCAN_W); return true; } const ipa = (() => { const hi = us.includes('ipa='), hl = /(rst|t)=\d+k/.test(us); return hi && hl; })(); if (ipa) { c.actionLogger.log(`[路径特征分析] URL判定为广告 (高精度Pornhub规则). URL: ${us.slice(-80)}`, LOG.SCAN_W); return true; } for (const r of HSR) { if (r.p.test(us)) { c.actionLogger.log(`[路径特征分析] URL判定为广告 (高权重规则: ${r.r}). URL: ${us.slice(-80)}`, LOG.SCAN_W); return true; } } try { let u; try { u = new URL(us, window.location.href); } catch (e) { return false; } s = 0; rs = []; const lr = R.S.fl, lm = us.match(lr.p); if (lm && lm[1]) { const rh = parseInt(lm[1], 10); if (!isNaN(rh) && rh < lr.mh) { s += lr.s; rs.push(`FilenameLowRes[${rh}p < ${lr.mh}p]: +${lr.s}`); } } const xm = us.match(R.S.xh.p); if (xm) { const a = parseInt(xm[1], 10) * parseInt(xm[2], 10); if (a < R.S.xh.ma) { s += R.S.xh.s; rs.push(`xHamsterLowRes: +${R.S.xh.s}`); } } if (u.searchParams.size > R.S.pct) { s += R.S.pcs; rs.push(`ParamCount[>${R.S.pct}]: +${R.S.pcs}`); } u.searchParams.forEach((v, k) => { R.Q.forEach(r => { if (r.p.test(k)) { s += r.s; rs.push(`ParamKey[${k}]: +${r.s}`); } }); }); const ps = u.pathname.split('/').filter(Boolean); ps.forEach(sg => { if (sg.length > R.S.lst && /^[a-zA-Z0-9]+$/.test(sg) && !its) { s += R.S.lss; rs.push(`LongSegment[${sg.substring(0, 10)}...]: +${R.S.lss}`); } }); if (s >= ST) { c.actionLogger.log(`[路径特征分析] URL判定为广告 (结构分析分数: ${s}/${ST}). 原因: ${rs.join(', ')}. URL: ${us.slice(-80)}`, LOG.SCAN_W); return true; } } catch (e) { return false; } return false; } class DOMScanner { constructor(c) { this.context = c; this.observer = null; this.heartbeatTimer = null; this.isStopped = false; this.boundScanPage = Utils.debounce(this.scanPage.bind(this), 200); this.boundHandleScroll = Utils.debounce(this.handleScroll.bind(this), 500); } init() { if (this.observer) { this.isStopped = false; return; } if (/(captcha|auth|login|signin|widget|comment|plugin|share|button|ads?|tracker|analytics|pixel|sync|oauth|verify|challenge|static|assets)/i.test(window.location.href)) { this.isStopped = true; return; } if (Utils.isHighNoisePage()) { this.context.actionLogger.log("DOM扫描器: 检测到高噪音列表页,已自动休眠。", LOG.SCAN); this.isStopped = true; return; } this.context.actionLogger.log("DOM扫描器已激活,开始搜索视频源。", LOG.SCAN); this.isStopped = false; this.scanPage(); this.observer = new MutationObserver(() => this.boundScanPage()); this.observer.observe(document.body || document.documentElement, { childList: true, subtree: true }); _el(window, 'scroll', this.boundHandleScroll, { passive: true }); if (!this.heartbeatTimer) this.heartbeatTimer = setInterval(() => { if (!this.isStopped) this.scanPage(); }, 1500); } stop() { this.isStopped = true; } handleScroll() { if (this.isStopped) return; this.scanPage(); } scanPage() { if (this.isStopped || this.context.coreLogic.isSystemHalted) return; for (const m of this.context.coreLogic.findAllVideosAndAudioInPage()) this.processMediaElement(m); } async processMediaElement(m) { if (m.id === C.PLAYER) return false; const ts = m.src || m.querySelector('source[src]')?.src; if (!ts) return false; const { coreLogic: cl } = this.context; if (m.dataset.dmzIsAd === 'true') { cl.neutralizeOriginalPlayer(m, 'passive'); return false; } if (isPreRollAdByPathFeatures(ts, this.context)) { m.dataset.dmzIsAd = 'true'; cl.neutralizeOriginalPlayer(m, 'passive'); return false; } if (m.dataset.dmzIgnored === 'true') return false; const r = m.getBoundingClientRect(), cs = window.getComputedStyle(m); if (r.width < 150 || r.height < 150) { m.dataset.dmzIgnored = 'true'; return false; } if (cs.display === 'none' || cs.visibility === 'hidden') return false; if (m.hasAttribute('loop') && m.muted && m.hasAttribute('autoplay') && !m.hasAttribute('controls')) { m.dataset.dmzIgnored = 'true'; return false; } cl.neutralizeOriginalPlayer(m, 'active'); if (m.dataset.dmzSourceLocked === ts) return false; m.dataset.dmzSourceLocked = ts; const sd = { rect: { width: r.width, height: r.height }, computedStyle: { display: cs.display, visibility: cs.visibility } }; if (m.tagName !== 'VIDEO') return false; const ci = { mediaElement: m, survey: sd, url: ts, sourceName: SRC.DOM }; for (const k in m.dataset) { const v = m.dataset[k]; if (typeof v === 'string' && (v.includes('http') || v.includes('{'))) { try { if (v.trim().startsWith('{')) { const c = JSON.parse(v), du = c.src || c.url || c.videoUrl; if (du && typeof du === 'string' && du.startsWith('http')) { ci.url = du; ci.sourceName = SRC.ATTR; break; } } else if (v.startsWith('http')) { if (!isPreRollAdByPathFeatures(v, this.context)) { ci.url = v; ci.sourceName = SRC.ATTR; break; } } } catch (e) { } } } cl.addTakeoverCandidate(ci); return true; } } const NetworkInterceptor = Utils.createSwitchableModule({ name: 'NetworkInterceptor', originalXhrOpen: null, originalFetch: null, isActive: false, activate(c) { if (this.isActive) return; this.isActive = true; const { coreLogic: cl, playerManager: pm, actionLogger: al } = c, iv = (t) => typeof t === 'string' && t.length >= 10 && t.trim().startsWith('#EXTM3U') && t.includes('#EXTINF'); this.originalXhrOpen = XMLHttpRequest.prototype.open; this.originalFetch = window.fetch; XMLHttpRequest.prototype.open = function (m, u, ...r) { if (typeof u === 'string' && !(pm.isInternalRequest && u.startsWith('blob:'))) { this.addEventListener('load', () => { if (this.status >= 200 && this.status < 400 && this.responseText) { if (Utils.isM3U8(String(u)) || iv(this.responseText)) { al.log(`[网络拦截-XHR] 捕获到💎<高价值>信号: ${String(u).slice(-80)}`, LOG.ATT); cl.addTakeoverCandidate({ url: String(u), sourceName: SRC.XHR, m3u8Text: this.responseText }); } } }, { once: true }); } return NetworkInterceptor.originalXhrOpen.apply(this, [m, String(u), ...r]); }; window.fetch = async function (...a) { const r = new Request(a[0], a[1]); if (pm.isInternalRequest && r.url.startsWith('blob:')) return NetworkInterceptor.originalFetch.apply(this, a); const rs = await NetworkInterceptor.originalFetch.apply(this, a); if (rs.ok) { const cr = rs.clone(), im = Utils.isM3U8(r.url) || (rs.headers.get('content-type') || '').includes('mpegurl'); if (im) { try { const t = await cr.text(); if (iv(t)) { al.log(`[网络拦截-Fetch] 捕获到💎<高价值>信号: ${r.url.slice(-80)}`, LOG.ATT); cl.addTakeoverCandidate({ url: r.url, sourceName: SRC.FETCH, m3u8Text: t }); } } catch (e) { } } } return rs; }; }, deactivate() { if (!this.isActive) return; if (this.originalXhrOpen) { XMLHttpRequest.prototype.open = this.originalXhrOpen; this.originalXhrOpen = null; } if (this.originalFetch) { window.fetch = this.originalFetch; this.originalFetch = null; } this.isActive = false; } }); const PlayerHooker = Utils.createSwitchableModule({ name: 'PlayerHooker', targets: C.TARGETS, originalPlayers: {}, isActive: false, extractSource: (c) => { if (!c) return null; const s = c.source ?? c.video?.url ?? c.url ?? c.src; if (s && typeof s === 'string') return s; if (Array.isArray(c.sources)) { for (const so of c.sources) { if (so.src && typeof so.src === 'string') return so.src; } } return null; }, activate(c) { if (this.isActive) return; this.isActive = true; const { actionLogger: al, coreLogic: cl, diagnosticsTool: dt } = c; this.targets.forEach(n => { try { Object.defineProperty(window, n, { configurable: true, set: (p) => { if (!this.originalPlayers[n]) { this.originalPlayers[n] = p; al.log(`监测到播放器库 ${n} 加载`, LOG.HOOK); } }, get: () => { const o = this.originalPlayers[n]; return (...a) => { const vu = this.extractSource(a[0]); if (vu) { dt.logEvent({ type: 'PLAYER_HOOK', message: `[${n}] 调用被成功拦截, 提取到源.` }); cl.addTakeoverCandidate({ url: vu, sourceName: SRC.HOOK(n) }); return new Proxy({}, { get: (t, p) => (p in t) ? t[p] : (() => { }) }); } let dm = ''; try { dm = JSON.stringify(a[0], null, 2); } catch (e) { dm = `[无法序列化配置: ${e.message}]`; } dt.logEvent({ type: LOG.HOOK_F, message: `[${n}] 调用被拦截, 但未提取到源. Config: \n${dm}` }); return o ? Reflect.construct(o, a) : null; }; } }); } catch (e) { al.log(`接管播放器 ${n} 失败: ${e.message}`, LOG.ERROR); } }); }, deactivate() { if (!this.isActive) return; this.targets.forEach(n => { if (this.originalPlayers[n]) { try { Object.defineProperty(window, n, { value: this.originalPlayers[n], writable: true, configurable: true, enumerable: true }); } catch (e) { console.warn(`[${SCRIPT_NAME}] 恢复播放器 ${n} 失败: ${e.message}`); } } }); this.originalPlayers = {}; this.isActive = false; } }); const DecryptionHooker = Utils.createSwitchableModule({ name: 'DecryptionHooker', originalAtob: null, isActive: false, activate(c) { if (this.isActive) return; this.isActive = true; const { actionLogger: al, coreLogic: cl } = c; try { this.originalAtob = window.atob; window.atob = function (...a) { const r = DecryptionHooker.originalAtob.apply(window, a); if (typeof r === 'string' && (Utils.isM3U8(r) || /\.(mp4|flv|webm)(\?.*)?$/.test(r))) { al.log(`劫持 atob 成功,发现可疑视频链接。`, LOG.DECRYPT); cl.addTakeoverCandidate({ url: r, sourceName: SRC.ATOB }); } return r; }; } catch (e) { al.log(`劫持 window.atob 失败: ${e.message}`, LOG.ERROR); this.deactivate(c); } }, deactivate() { if (!this.isActive) return; if (this.originalAtob) { window.atob = this.originalAtob; this.originalAtob = null; } this.isActive = false; } }); class HighPrecisionJsonInspector { constructor() { this.FBP = [/googleads|doubleclick|syndication|adservice|criteo|taboola/i, /favicon\.ico|blank\.mp4|empty\.mp4|\.gif|\.css|\.js/i, /_TPL_|media=hls4A|analyt|pixel|beacon|stat/i, /\.(jpg|jpeg|png|webp|svg|bmp|tiff)(\?|$)/i, /\.(vtt|srt|ass|dfxp)(\?|$)/i]; this.FKP = [/thumbnail|cover|poster|sprite|mask|icon|logo|avatar/i, /ad_url|vast|vpaid|preroll|postroll|midroll|bumper/i, /subtitle|caption|track/i, /analytics|beacon|log|report|config|setting/i]; this.VE = /\.(m3u8|mpd|mp4|flv|webm|mkv|avi|mov)(\?|$)/i; this.HCK = /^(src|url|play_?url|video_?url|stream|hls|dash|file)$/i; this.MCK = /video|content|media|path|source/i; this.SK = /preview|trailer|teaser|sample|snippet|intro/i; this.SUP = /preview|trailer|limit=\d+|demo/i; this.processedUrls = new Set(); } evaluateScore(k, v, po = {}) { if (typeof v !== 'string' || v.length < 10) return { passed: false, score: 0 }; if (this.processedUrls.has(v)) return { passed: false, score: 0 }; const cu = v.trim(); if (!/^(https?:|\/\/|blob:)/i.test(cu)) return { passed: false, score: 0 }; if (this.FBP.some(p => p.test(cu))) return { passed: false, score: 0 }; if (this.FKP.some(p => p.test(k))) return { passed: false, score: 0 }; let s = 0, rs = []; const ive = this.VE.test(cu), im = /\.m3u8|\.mpd/i.test(cu); if (ive) { if (im) { s += 80; rs.push("Stream"); } else { s += 30; rs.push("VideoFile"); if (/\/m3u8\/|\/hls\//i.test(cu)) { s -= 30; rs.push("ConflictPath"); } } } else { s -= 20; rs.push("NoVideoExt"); } if (cu.includes('?')) { if (ive) { s += 10; if (/[?&](sign|token|auth|key|secret|v=|t=|st=)/i.test(cu)) { s += 40; rs.push("AuthParam"); } } } else { if (im) { s -= 40; rs.push("M3U8_NoAuth"); } else { s -= 15; rs.push("NoParams"); } } if (this.HCK.test(k)) { s += 40; rs.push("StrongKey"); } else if (this.MCK.test(k)) { s += 15; rs.push("MedKey"); } if (this.SK.test(k)) { s -= 40; rs.push("BadKey"); } if (this.SUP.test(cu)) { s -= 30; rs.push("BadUrl"); } if (po) { const pk = Object.keys(po).join(' ').toLowerCase(); if (po.type && /video|mpegURL/i.test(po.type)) { s += 30; rs.push("CtxType"); } if (pk.includes('ad_') || pk.includes('vast')) { s -= 50; rs.push("CtxAd"); } if (po.duration && !isNaN(parseFloat(po.duration))) { const d = parseFloat(po.duration); if (d > 600) { s += 20; rs.push("LongDur"); } } } return { passed: s >= 40, score: s, reason: rs.join('+') }; } } const JsonInspector = Utils.createSwitchableModule({ name: 'JsonInspector', originalParse: null, isActive: false, inspector: null, activate(c) { if (this.isActive) return; this.isActive = true; this.originalParse = JSON.parse; const { coreLogic: cl, actionLogger: al } = c; this.inspector = new HighPrecisionJsonInspector(); const po = new WeakSet(), cc = (co, col = []) => { const f = (o, d) => { if (d > 20 || !o || typeof o !== 'object' || po.has(o)) return; po.add(o); for (const k in o) { if (Object.prototype.hasOwnProperty.call(o, k)) { const v = o[k]; if (typeof v === 'string') { const r = this.inspector.evaluateScore(k, v, o); if (r.passed) col.push({ url: v.trim(), score: r.score, key: k, reason: r.reason, jsonContext: o }); } else if (typeof v === 'object') f(v, d + 1); } } }; f(co, 0); return col; }; let prc = null; JSON.parse = function (t, r) { const res = JsonInspector.originalParse.call(JSON, t, r); if (!t || t.length < 50) return res; if (prc === null) { prc = Utils.isHighNoisePage(); if (prc) al.log("JSON Inspector: 检测到列表/搜索页,进入休眠模式。", LOG.MODULE); } if (prc === true) return res; if (!/\.(m3u8|mp4|flv)|play|video|stream/i.test(t)) return res; const ap = () => { try { const cs = cc(res); if (cs.length === 0) return; cs.sort((a, b) => b.score - a.score); const tc = cs.slice(0, 3); let hn = false; tc.forEach(c => { if (!JsonInspector.inspector.processedUrls.has(c.url)) { JsonInspector.inspector.processedUrls.add(c.url); cl.addTakeoverCandidate({ url: c.url, sourceName: `${SRC.JSON} (Score:${c.score})`, jsonContext: c.jsonContext }); hn = true; } }); if (hn) { const s = tc.map(c => `[${c.score}]...${c.url.slice(-20)}`).join(', '); al.log(`JSON 深度挖掘完成: 发现 ${cs.length} 个候选,优选上报: ${s}`, LOG.DECRYPT); } } catch (e) { } }; if (window.requestIdleCallback) window.requestIdleCallback(ap); else setTimeout(ap, 100); return res; }; }, deactivate() { if (!this.isActive) return; if (this.originalParse) { JSON.parse = this.originalParse; this.originalParse = null; } this.isActive = false; this.inspector = null; } }); const SetAttributeHooker = Utils.createSwitchableModule({ name: 'SetAttributeHooker', originalSetAttribute: null, isActive: false, activate(c) { if (this.isActive) return; this.isActive = true; this.originalSetAttribute = Element.prototype.setAttribute; const { coreLogic: cl, actionLogger: al } = c; Element.prototype.setAttribute = function (n, v) { if ((this.tagName === 'VIDEO' || this.tagName === 'AUDIO' || this.tagName === 'SOURCE') && n.toLowerCase() === 'src' && typeof v === 'string' && v.startsWith('http')) { al.log(`SetAttribute Hooker 捕获到对 ${this.tagName} 的 src 设置。`, LOG.DECRYPT); const m = this.tagName === 'SOURCE' ? this.parentElement : this; cl.addTakeoverCandidate({ url: v, sourceName: SRC.SET, mediaElement: m }); } return SetAttributeHooker.originalSetAttribute.apply(this, arguments); }; }, deactivate() { if (!this.isActive) return; if (this.originalSetAttribute) { Element.prototype.setAttribute = this.originalSetAttribute; this.originalSetAttribute = null; } this.isActive = false; } }); class SettingsUI { constructor() { this.UI_CONFIG = [{ type: 'info', content: `💡 作者:bug大魔王: 脚本好用的话,点个赞,喜欢可以来tg群组交流 https://t.me/+HbQE7TdRe-4yMGM1 。` }, { group: '播放控制', items: [{ id: 'auto-play-toggle', label: '自动播放', type: 'switch', configKey: 'autoPlay' }, { id: 'smart-slice-toggle', label: '广告净化', type: 'switch', configKey: 'isSmartSlicingEnabled' }, { id: 'playback-rate-input', label: '默认播放倍速', type: 'number', configKey: 'defaultPlaybackRate', props: { step: "0.25", min: "0.5", max: "5.0" } }, { id: 'long-press-rate-input', label: '长按倍速', type: 'number', configKey: 'longPressRate', props: { step: "0.25", min: "1.0", max: "5.0" } }] }, { group: '高级设置', items: [{ id: 'max-retries-input', label: '网络最大重试次数 (快速失败建议为0)', type: 'number', configKey: 'maxRetryCount', props: { min: "0", max: "10" } }, { id: 'json-inspector-toggle', label: '深度JSON解析劫持 (增强成功率)', type: 'switch', configKey: 'enableJsonInspector' }, { id: 'set-attribute-hooker-toggle', label: '底层播放器属性劫持 (增强成功率)', type: 'switch', configKey: 'enableSetAttributeHooker' }] }, { group: '站点管理', items: [{ id: 'site-blacklist-input', label: '黑名单 (在这些网站上禁用脚本,一行一个)', type: 'textarea', configKey: 'blacklist', props: { rows: "8", id: "site-blacklist-input" } }, { id: 'add-to-blacklist-btn', label: '添加当前站点到黑名单', type: 'button' }] }]; this.UI_HANDLERS = { 'switch': { getValue: e => e.checked, setValue: (e, v) => { e.checked = !!v; } }, 'textarea': { getValue: e => e.value.split('\n').map(s => s.trim()).filter(Boolean), setValue: (e, v) => { e.value = Array.isArray(v) ? v.join('\n') : (v ?? ''); } }, 'number': { getValue: (e, i) => parseFloat(e.value) || (i.configKey === 'maxRetryCount' ? 3 : 1.0), setValue: (e, v) => { e.value = v; } }, }; } _createLabeledInput(i) { const l = _ce('label', { htmlFor: i.id, textContent: i.label }), inp = _ce(i.type === 'number' ? 'input' : 'textarea', { id: i.id, ...i.props }); if (i.type === 'number') inp.type = 'number'; return _ce('div', { className: C.COL }, [l, inp]); } generateSettingsContent() { const ca = _ce('div', { className: C.GRID }); this.UI_CONFIG.forEach(s => { if (s.type === 'info') { ca.appendChild(_ce('div', { className: C.INFO_C, innerHTML: s.content })); return; } const c = _ce('div', { className: C.CARD }); c.appendChild(_ce('h3', { className: C.TITLE, textContent: s.group })); s.items.forEach(i => { let e; switch (i.type) { case 'switch': e = _ce('div', { className: C.ITEM }, [_ce('label', { htmlFor: i.id, textContent: i.label }), _ce('label', { className: C.SW }, [_ce('input', { type: 'checkbox', id: i.id }), _ce('span', { className: C.SL })])]); break; case 'number': case 'textarea': e = this._createLabeledInput(i); break; case 'button': e = _ce('button', { id: i.id, className: `${C.BTN} ${C.B_ACT}`, textContent: i.label }); break; } if (e) c.appendChild(e); }); ca.appendChild(c); }); return ca; } loadDataToUI(p, d) { this.UI_CONFIG.flatMap(s => s.items ?? []).forEach(i => { if (!i.configKey) return; const e = p.querySelector(`#${i.id}`), h = this.UI_HANDLERS[i.type]; if (e && h) h.setValue(e, d[i.configKey], i); }); } saveDataFromUI(p) { const nc = {}; this.UI_CONFIG.flatMap(s => s.items ?? []).forEach(i => { if (!i.configKey) return; const e = p.querySelector(`#${i.id}`), h = this.UI_HANDLERS[i.type]; if (e && h) nc[i.configKey] = h.getValue(e, i); }); return nc; } } class UnifiedPanelManager { constructor(c) { this.context = c; this.hostElement = null; this.infoUpdateInterval = null; this.elements = {}; } initGesture() { let t = null, sx, sy; const c = () => { if (t) { clearTimeout(t); t = null; } }; _el(document, 'touchstart', (e) => { if (e.touches.length === 2) { sx = e.touches[0].clientX; sy = e.touches[0].clientY; t = setTimeout(() => { this.show(); if (navigator.vibrate) navigator.vibrate(50); }, 1000); } else c(); }, { capture: true, passive: true }); _el(document, 'touchmove', (e) => { if (t && (Math.abs(e.touches[0].clientX - sx) > 20 || Math.abs(e.touches[0].clientY - sy) > 20)) c(); }, { capture: true, passive: true }); ['touchend', 'touchcancel'].forEach(e => _el(document, e, c, { capture: true, passive: true })); } create() { if (this.hostElement) return; this.hostElement = _ce('div', { id: 'dmz-unified-panel-host' }); const pp = (e) => { e.stopPropagation(); if (e.type === 'contextmenu') e.preventDefault(); }; ['touchstart', 'touchmove', 'touchend', 'mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'contextmenu', 'wheel'].forEach(e => this.hostElement.addEventListener(e, pp, { passive: false })); document.body.appendChild(this.hostElement); const sr = this.hostElement.attachShadow({ mode: 'open' }); sr.appendChild(_ce('style', { textContent: UNIFIED_PANEL_CSS })); const w = _ce('div', { className: 'dmz-unified-panel-wrapper' }), h = _ce('div', { className: 'panel-header' }, [_ce('div', { className: 'title-bar' }, [_ce('h3', { innerHTML: `💡 ${SCRIPT_NAME}` }), _ce('button', { className: 'close-btn', title: '关闭', textContent: '×' })])]), n = _ce('div', { className: 'dmz-panel-nav' }, [_ce('button', { id: 'nav-btn-diagnostics', className: 'dmz-nav-btn', textContent: '诊断报告' }), _ce('button', { id: 'nav-btn-settings', className: 'dmz-nav-btn', textContent: '功能设置' })]), c = _ce('div', { className: 'dmz-unified-panel-content' }), dp = _ce('div', { id: 'pane-diagnostics', className: 'dmz-panel-pane' }, [this.context.infoPanelManager.generateDiagnosticsContent()]), sp = _ce('div', { id: 'pane-settings', className: 'dmz-panel-pane' }, [this.context.settingsUI.generateSettingsContent()]); c.append(dp, sp); const f = _ce('div', { className: 'dmz-unified-panel-footer' }, [_ce('button', { id: 'dmz-settings-reset-btn', className: `${C.BTN} ${C.B_RESET}`, textContent: '恢复默认' }), _ce('button', { id: 'dmz-settings-save-btn', className: `${C.BTN} ${C.B_SAVE}`, textContent: '保存并应用' })]); w.append(h, n, c, f); sr.appendChild(w); this.elements = { shadowRoot: sr, wrapper: w, footer: f, navBtnDiagnostics: sr.querySelector('#nav-btn-diagnostics'), navBtnSettings: sr.querySelector('#nav-btn-settings'), paneDiagnostics: sr.querySelector('#pane-diagnostics'), paneSettings: sr.querySelector('#pane-settings'), saveBtn: sr.querySelector('#dmz-settings-save-btn'), resetBtn: sr.querySelector('#dmz-settings-reset-btn'), closeBtn: sr.querySelector('.close-btn'), addToBlacklistBtn: sr.querySelector('#add-to-blacklist-btn'), }; this.bindEvents(); } bindEvents() { this.elements.closeBtn.addEventListener('click', () => this.hide()); this.elements.navBtnDiagnostics.addEventListener('click', () => this.switchToPane('diagnostics')); this.elements.navBtnSettings.addEventListener('click', () => this.switchToPane('settings')); this.elements.saveBtn.addEventListener('click', async () => { const nc = this.context.settingsUI.saveDataFromUI(this.elements.shadowRoot); await this.context.settingsManager.save(nc); Utils.showButtonFeedback(this.elements.saveBtn, { successText: '✔ 已保存!', successBgColor: '#28a745' }); this.context.frameCommunicator.showNotification('设置已保存并应用。'); }); this.elements.resetBtn.addEventListener('click', async () => { if (window.confirm('您确定要将所有设置恢复为默认值吗?此操作不可撤销。')) { const nc = await this.context.settingsManager.reset(); this.context.settingsUI.loadDataToUI(this.elements.shadowRoot, nc); } }); if (this.elements.addToBlacklistBtn) { this.elements.addToBlacklistBtn.addEventListener('click', () => { const bt = this.elements.shadowRoot.querySelector('#site-blacklist-input'), hn = window.location.hostname, l = bt.value.split('\n').map(s => s.trim()).filter(Boolean); if (l.includes(hn)) return this.context.frameCommunicator.showNotification(`"${hn}" 已在黑名单中。`); l.push(hn); bt.value = l.join('\n'); this.context.frameCommunicator.showNotification(`已将 "${hn}" 添加到黑名单。保存后将在所有页面生效。`); }); } const cc = this.elements.shadowRoot.querySelector('.dmz-unified-panel-content'); if (cc) { cc.addEventListener('click', (e) => { const ch = e.target.closest('.log-collapsible'); if (!ch) return; const c = ch.nextElementSibling; if (c && c.classList.contains('log-collapsed-content')) { const v = c.style.display !== 'none'; c.style.display = v ? 'none' : 'block'; const i = ch.querySelector('strong'); if (v) i.textContent = i.textContent.replace('[-]', '[+]').replace('收起', '展开'); else i.textContent = i.textContent.replace('[+]', '[-]').replace('展开', '收起'); } }); } } switchToPane(pn) { const id = pn === 'diagnostics'; this.elements.navBtnDiagnostics.classList.toggle('active', id); this.elements.paneDiagnostics.classList.toggle('active', id); this.elements.navBtnSettings.classList.toggle('active', !id); this.elements.paneSettings.classList.toggle('active', !id); this.elements.footer.classList.toggle('active', !id); if (this.infoUpdateInterval) clearInterval(this.infoUpdateInterval); if (id) { this.context.infoPanelManager.renderDeveloperLog(); this.context.infoPanelManager.update(); this.infoUpdateInterval = setInterval(() => { this.context.infoPanelManager.update(); this.context.infoPanelManager.renderDeveloperLog(); }, 1500); } } show() { if (!IS_TOP_FRAME) return; if (!this.hostElement) this.create(); this.hostElement.classList.add('visible'); document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; this.context.settingsUI.loadDataToUI(this.elements.shadowRoot, this.context.settingsManager.config); this.switchToPane('diagnostics'); } hide() { if (this.hostElement) this.hostElement.classList.remove('visible'); document.documentElement.style.overflow = ''; document.body.style.overflow = ''; if (this.infoUpdateInterval) clearInterval(this.infoUpdateInterval); } } function scanForFlashvars(c) { if (Utils.isHighNoisePage()) return; const { actionLogger: al, coreLogic: cl } = c; al.log("启动 Flashvars 扫描器...", LOG.SCAN); try { const ss = Array.from(document.querySelectorAll('script')), ps = ss.find(s => s.textContent.includes('var flashvars_')); if (!ps) { al.log("Flashvars 扫描失败:未找到播放器配置脚本。", LOG.SCAN_W); return; } const m = ps.textContent.match(/var\s+flashvars_\d+\s*=\s*({[\s\S]*?});/); if (!m || !m[1]) { al.log("Flashvars 扫描失败:无法匹配到 flashvars 对象。", LOG.SCAN_W); return; } const fv = JSON.parse(m[1]); if (fv && fv.mediaDefinitions) { let bq = -1, bu = null; fv.mediaDefinitions.forEach(m => { const qn = parseInt(m.quality, 10); if (m.videoUrl && qn > bq) { bq = qn; bu = m.videoUrl; } }); if (bu) { al.log(`Flashvars 扫描成功!发现最高清晰度 ${bq}p 的视频源。`, LOG.SUCC); cl.addTakeoverCandidate({ url: bu, sourceName: '页面数据扫描 (flashvars)' }); } else al.log("Flashvars 扫描警告:在 mediaDefinitions 中未找到有效的 videoUrl。", LOG.SCAN_W); } else al.log("Flashvars 扫描警告:解析出的对象中缺少 mediaDefinitions。", LOG.SCAN_W); } catch (e) { al.log(`Flashvars 扫描时发生严重错误: ${e.message}`, LOG.ERROR); } } function scanForEmbeddedData(c) { if (Utils.isHighNoisePage()) return; const { actionLogger: al, coreLogic: cl } = c; al.log("启动嵌入式数据扫描器...", LOG.SCAN); try { const ss = Array.from(document.querySelectorAll('script:not([src])')), ups = [/(?:'|")\w*url(?:'|")\s*:\s*(?:'|")([^'"]+\.(?:mp4|m3u8)[^'"]*)(?:'|")/gi, /(?:'|")\w*source(?:'|")\s*:\s*(?:'|")([^'"]+\.(?:mp4|m3u8)[^'"]*)(?:'|")/gi, /(?:'|")\w*src(?:'|")\s*:\s*(?:'|")([^'"]+\.(?:mp4|m3u8)[^'"]*)(?:'|")/gi, /['"](https?:\/\/[^'"]+\.(?:mp4|m3u8)[^'"]*)['"]/gi]; for (const s of ss) { const t = s.textContent; if (t.length < 200) continue; for (const p of ups) { let m; while ((m = p.exec(t)) !== null) { const pu = m[1].replace(/\\/g, ''); if (isPreRollAdByPathFeatures(pu, c)) { al.log(`嵌入式扫描器发现广告链接,已跳过: ${pu}`, LOG.SCAN_W); continue; } al.log(`嵌入式扫描器发现潜在视频源!`, LOG.SUCC); cl.addTakeoverCandidate({ url: pu, sourceName: '页面数据扫描 (嵌入式)' }); return; } } } } catch (e) { al.log(`嵌入式数据扫描时发生错误: ${e.message}`, LOG.ERROR); } } const MANAGED_MODULES = [{ module: PlayerHooker, configKey: 'enablePlayerTakeover' }, { module: NetworkInterceptor, configKey: 'enableNetworkInterceptor' }, { module: DecryptionHooker, configKey: 'enableDecryptionHook' }, { module: JsonInspector, configKey: 'enableJsonInspector' }, { module: SetAttributeHooker, configKey: 'enableSetAttributeHooker' }]; (async function main() { const al = new ActionLogger(), c = { actionLogger: al }; c.diagnosticsTool = new DiagnosticsTool(c); c.settingsManager = new SettingsManager(c); c.frameCommunicator = new FrameCommunicator(c); c.styleManager = new StyleManager(c); c.playerManager = new PlayerManager(c); c.coreLogic = new CoreLogic(c); c.spaNavigator = new SPANavigator(c); c.domScanner = new DOMScanner(c); c.settingsUI = new SettingsUI(c); c.infoPanelManager = new InfoPanelManager(c); c.unifiedPanelManager = new UnifiedPanelManager(c); al.setContext(c); window.diagnosticsTool = c.diagnosticsTool; IframeTerminator.init(c); IframeTerminator.start(); await c.settingsManager.load(); const h = window.location.hostname, ib = c.settingsManager.config.blacklist.some(d => { try { return Utils.wildcardToRegex(d.trim()).test(h); } catch (e) { c.actionLogger.log(`黑名单规则 "${d}" 无效,已跳过`, LOG.WARN); return false; } }); if (ib) { c.actionLogger.log(`已根据黑名单在 ${h} 停用。`, LOG.INIT); IframeTerminator.stop(); return; } c.actionLogger.log(`视频助手已在页面启动。 (上下文: ${IS_TOP_FRAME ? 'Top' : 'iFrame'})`, LOG.INIT); console.log(`[${SCRIPT_NAME}] v${SCRIPT_VERSION} 已在 ${window.location.href} 激活`); c.frameCommunicator.init(); if (IS_TOP_FRAME) { c.spaNavigator.init(); c.unifiedPanelManager.initGesture(); registerMenuCommands(c); c.coreLogic.initializeUserIntentObserver(); c.coreLogic.startSandboxCountdown(); const sas = () => { scanForFlashvars(c); scanForEmbeddedData(c); }; if (document.readyState === 'loading') _el(window, 'DOMContentLoaded', sas, { once: true }); else sas(); } MANAGED_MODULES.forEach(({ module: m, configKey: k }) => { if (c.settingsManager.config[k]) m.enable(c); }); if (!IS_TOP_FRAME) { setTimeout(() => { c.actionLogger.log("士兵:已准备就绪,向指挥官报道并请求指令...", LOG.COMM); c.frameCommunicator.postToTopFrame({ type: MSG.QUERY }); }, 100); } const ss = () => c.domScanner.init(); document.readyState === 'loading' ? _el(window, 'DOMContentLoaded', ss, { once: true }) : ss(); })().catch(e => console.error(`[${SCRIPT_NAME}] 启动时发生致命错误:`, e)); function registerMenuCommands(c) { if (!IS_TOP_FRAME) return; GM_registerMenuCommand(`📊 ${SCRIPT_NAME} 诊断与设置`, () => c.unifiedPanelManager.show()); } })();