Greasy Fork

来自缓存

Greasy Fork is available in English.

2025/10月最新VIP视频解析自动4K播放(针对爱奇艺/腾讯/芒果TV优化)

自动检测视频播放页并解析替换播放框,支持多接口切换,实时监听URL变化,针对爱奇艺/腾讯/芒果TV优化

// ==UserScript==
// @name         2025/10月最新VIP视频解析自动4K播放(针对爱奇艺/腾讯/芒果TV优化)
// @namespace    https://baidu.com/
// @version      1.1.0
// @description  自动检测视频播放页并解析替换播放框,支持多接口切换,实时监听URL变化,针对爱奇艺/腾讯/芒果TV优化
// @author       User
// @match        *://*.iqiyi.com/*
// @match        *://v.qq.com/*
// @match        *://*.mgtv.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=isyour.love
// @run-at       document-idle
// @grant        GM_setValue
// @grant        GM_getValue
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  const IFRAME_ID = 'tm-vip-parser-iframe';
  const CONTAINER_DATA_FLAG = 'tmVipParserContainer';
  const CONFIG_KEY = 'vip_parser_config';
  
  const PARSE_INTERFACES = [
    {"name": "789解析", "url": "https://jiexi.789jiexi.icu:4433/?url="},
    {"name": "聚合解析", "url": "https://video.isyour.love/player/getplayer?url="},
    {"name": "冰豆解析", "url": "https://bd.jx.cn/?url="},
    {"name": "973解析", "url": "https://jx.973973.xyz/?url="},
    {"name": "Player-JY", "url": "https://jx.playerjy.com/?url="},
    {"name": "虾米视频解析", "url": "https://jx.xmflv.com/?url="},
    {"name": "CK", "url": "https://www.ckplayer.vip/jiexi/?url="},
    {"name": "七哥解析", "url": "https://jx.nnxv.cn/tv.php?url="},
    {"name": "夜幕", "url": "https://www.yemu.xyz/?url="},
    {"name": "盘古", "url": "https://www.pangujiexi.com/jiexi/?url="},
    {"name": "playm3u8", "url": "https://www.playm3u8.cn/jiexi.php?url="},
    {"name": "七七云解析", "url": "https://jx.77flv.cc/?url="},
    {"name": "芒果TV2", "url": "https://im1907.top/?jx="},
    {"name": "HLS解析", "url": "https://jx.hls.one/?url="}
  ];
  
  const DEFAULT_CONFIG = {
    enabled: true,
    selectedInterface: 0,
    sites: {
      iqiyi: true,
      qq: true,
      mgtv: true
    }
  };

  let parseActive = false;
  let currentContainer = null;
  let lastUrl = location.href;
  let urlCheckTimer = null;
  let currentSite = getSiteType();

  function getConfig() {
    try {
      const saved = GM_getValue(CONFIG_KEY);
      if (saved) {
        const config = JSON.parse(saved);
        return { ...DEFAULT_CONFIG, ...config, sites: { ...DEFAULT_CONFIG.sites, ...config.sites } };
      }
    } catch (e) {
      console.error('[解析] 获取配置失败:', e);
    }
    return DEFAULT_CONFIG;
  }

  function saveConfig(config) {
    try {
      GM_setValue(CONFIG_KEY, JSON.stringify(config));
    } catch (e) {
      console.error('[解析] 保存配置失败:', e);
    }
  }

  function getCurrentParsePrefix() {
    const config = getConfig();
    const index = config.selectedInterface || 0;
    return PARSE_INTERFACES[index].url;
  }

  function getSiteType() {
    const host = location.hostname;
    if (/iqiyi\.com$/i.test(host)) return 'iqiyi';
    if (/v\.qq\.com$/i.test(host)) return 'qq';
    if (/mgtv\.com$/i.test(host)) return 'mgtv';
    return null;
  }

  function isVideoPage() {
    const url = location.href;
    const host = location.hostname;
    const hasHtmlInUrl = /\.html?/i.test(url);
    let hasPlayer = false;
    
    if (/iqiyi\.com$/i.test(host)) {
      hasPlayer = !!(
        document.querySelector('video[src^="blob:"]') ||
        document.querySelector('[data-player-hook="videolayer"]') ||
        document.querySelector('.iqp-player-videolayer') ||
        document.querySelector('#videoContent')
      );
    } else if (/v\.qq\.com$/i.test(host)) {
      hasPlayer = !!(
        document.querySelector('#player-container') ||
        document.querySelector('.txp_videos_container video') ||
        document.querySelector('[data-mvp-dom="player"]')
      );
    } else if (/mgtv\.com$/i.test(host)) {
      hasPlayer = !!(
        document.querySelector('video[src^="blob:"]') ||
        document.querySelector('#mgtv-player-wrap') ||
        document.querySelector('.mango-player')
      );
    }

    return hasHtmlInUrl && hasPlayer;
  }

  function extractCanonicalHtmlUrl(href) {
    try {
      const u = new URL(href);
      let path = u.pathname || '/';
      const lower = path.toLowerCase();
      let cutIdx = lower.indexOf('.html');
      if (cutIdx >= 0) {
        cutIdx += 5;
      } else {
        const htmIdx = lower.indexOf('.htm');
        if (htmIdx >= 0) cutIdx = htmIdx + 4;
      }
      if (cutIdx && cutIdx > 0) {
        path = path.slice(0, cutIdx);
      } else {
        if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1);
      }
      return u.origin + path;
    } catch (e) {
      return href.split('#')[0].split('?')[0];
    }
  }

  function isElementVisible(el) {
    if (!el) return false;
    const s = window.getComputedStyle(el);
    if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return false;
    const r = el.getBoundingClientRect();
    return r.width > 50 && r.height > 50;
  }

  function getSiteSpecificSelectors() {
    const host = location.hostname;
    const generic = [
      'video',
      '#mod_player', '.mod_player', '.txp_player', '#tenvideo_player', '.tenvideo_player', '#player_container', '#player', '.player',
      '#flashbox', '.iqp-player', '#iqp-iframe',
      '#mgtv-player-wrap', '#mgtv-player', '.mgtv-player-wrap', '#player-box',
      '[class*="player"]', '[id*="player"]', '[class*="video"]', '[id*="video"]',
      'iframe[src*="iqiyi" i]', 'iframe[src*="v.qq.com" i]', 'iframe[src*="mgtv" i]'
    ];
    if (/iqiyi\.com$/i.test(host)) {
      return [
        '#videoContent', '#video',
        '[data-player-hook="videolayer"], .iqp-player-videolayer',
        '[data-player-hook="container"]', '[data-player-hook="outlayer"]',
        '#player', '.iqp-player', '#iqp-iframe',
        '#flashbox',
        ...generic
      ];
    }
    if (/v\.qq\.com$/i.test(host)) {
      return [
        '#player-container', '.player__container',
        '#player', '[data-mvp-dom="player"]',
        '.txp_videos_container', '._mod_thumbplayer_container_',
        '.mod_player', '#mod_player',
        ...generic
      ];
    }
    if (/mgtv\.com$/i.test(host)) {
      return [
        '#mgtv-player', '#player-box',
        '#mgtv-player-wrap', '.mgtv-player-wrap',
        ...generic
      ];
    }
    return generic;
  }

  function findBestPlayerElement() {
    const selectors = getSiteSpecificSelectors();
    const candidates = [];
    for (const sel of selectors) {
      const els = document.querySelectorAll(sel);
      for (const el of els) {
        if (el && isElementVisible(el) && !el.dataset[CONTAINER_DATA_FLAG]) {
          const rect = el.getBoundingClientRect();
          const area = rect.width * rect.height;
          const ratio = rect.width / (rect.height || 1);
          candidates.push({ el, area, ratio });
        }
      }
      if (candidates.length > 0) break;
    }
    if (candidates.length === 0) return null;
    candidates.sort((a, b) => {
      const scoreA = a.area * (a.ratio >= 1.3 && a.ratio <= 2.0 ? 1.2 : 1.0);
      const scoreB = b.area * (b.ratio >= 1.3 && b.ratio <= 2.0 ? 1.2 : 1.0);
      return scoreB - scoreA;
    });
    return candidates[0].el;
  }

  function getSiteContainer(preferredEl) {
    const host = location.hostname;
    if (/iqiyi\.com$/i.test(host)) {
      const videoLayer = document.querySelector('[data-player-hook="videolayer"], .iqp-player-videolayer');
      if (videoLayer && isElementVisible(videoLayer)) return videoLayer;
      const out = document.querySelector('[data-player-hook="outlayer"], .iqp-player');
      if (out && isElementVisible(out)) return out;
      const vc = document.querySelector('#videoContent');
      if (vc && isElementVisible(vc)) return vc;
      if (preferredEl && preferredEl !== document.body && preferredEl !== document.documentElement) return preferredEl;
      return null;
    }
    if (/v\.qq\.com$/i.test(host)) {
      const playerContainer = document.querySelector('#player-container, .player__container');
      if (playerContainer && isElementVisible(playerContainer)) return playerContainer;
      const player = document.querySelector('#player[data-mvp-dom="player"]');
      if (player && isElementVisible(player)) return player;
      const txpContainer = document.querySelector('._mod_thumbplayer_container_');
      if (txpContainer && isElementVisible(txpContainer)) return txpContainer;
      if (preferredEl && preferredEl !== document.body && preferredEl !== document.documentElement) return preferredEl;
      return null;
    }
    if (/mgtv\.com$/i.test(host)) {
      const mgtvPlayer = document.querySelector('#mgtv-player-wrap');
      if (mgtvPlayer && isElementVisible(mgtvPlayer)) return mgtvPlayer;
      const playerBox = document.querySelector('#player-box');
      if (playerBox && isElementVisible(playerBox)) return playerBox;
      if (preferredEl && preferredEl !== document.body && preferredEl !== document.documentElement) return preferredEl;
      return null;
    }
    return preferredEl;
  }

  function pauseVideos() {
    document.querySelectorAll('video').forEach(v => {
      try {
        v.pause();
        v.muted = true;
        v.volume = 0;
        if (v.src && v.src.startsWith('blob:')) {
          v.removeAttribute('src');
          v.load();
        }
      } catch (e) {}
    });

    document.querySelectorAll('audio').forEach(a => {
      try {
        a.pause();
        a.muted = true;
        a.volume = 0;
      } catch (e) {}
    });

    if (/iqiyi\.com$/i.test(location.hostname)) {
      try {
        if (window.Q && window.Q.player) {
          window.Q.player.pause();
        }
        document.querySelectorAll('iframe').forEach(iframe => {
          try {
            const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
            if (iframeDoc) {
              iframeDoc.querySelectorAll('video, audio').forEach(media => {
                media.pause();
                media.muted = true;
                media.volume = 0;
              });
            }
          } catch (e) {}
        });
      } catch (e) {}
    }

    if (/v\.qq\.com$/i.test(location.hostname)) {
      try {
        if (window.player && typeof window.player.pause === 'function') {
          window.player.pause();
        }
        if (window.PLAYER && typeof window.PLAYER.pause === 'function') {
          window.PLAYER.pause();
        }
      } catch (e) {}
    }

    if (/mgtv\.com$/i.test(location.hostname)) {
      try {
        if (window.player && typeof window.player.pause === 'function') {
          window.player.pause();
        }
      } catch (e) {}
    }
  }

  let pauseInterval = null;
  function startPauseMonitor() {
    if (pauseInterval) clearInterval(pauseInterval);
    pauseVideos();
    pauseInterval = setInterval(() => {
      pauseVideos();
    }, 500);
  }

  function stopPauseMonitor() {
    if (pauseInterval) {
      clearInterval(pauseInterval);
      pauseInterval = null;
    }
  }

  function ensureIframeIn(container, targetUrl) {
    if (!container) return false;
    if (container === document.body || container === document.documentElement) return false;
    container.dataset[CONTAINER_DATA_FLAG] = '1';

    let iframe = document.getElementById(IFRAME_ID);
    if (!iframe) {
      iframe = document.createElement('iframe');
      iframe.id = IFRAME_ID;
      iframe.setAttribute('allowfullscreen', 'true');
      iframe.setAttribute('allow', 'autoplay; fullscreen; picture-in-picture');
      iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-top-navigation');
      iframe.setAttribute('referrerpolicy', 'no-referrer');
      iframe.style.cssText = `
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        width: 100% !important;
        height: 100% !important;
        border: none !important;
        z-index: 2147483647 !important;
        background: #000 !important;
        opacity: 1 !important;
        visibility: visible !important;
        pointer-events: auto !important;
      `;
      document.body.appendChild(iframe);
      console.log('[解析] iframe 已插入 body');
    }

    if (iframe.src !== targetUrl) {
      iframe.src = targetUrl;
      console.log('[解析] iframe src 已更新:', targetUrl);
    }

    function updateIframePosition() {
      if (!container || !iframe) return;
      const rect = container.getBoundingClientRect();
      iframe.style.top = rect.top + 'px';
      iframe.style.left = rect.left + 'px';
      iframe.style.width = rect.width + 'px';
      iframe.style.height = rect.height + 'px';
    }

    updateIframePosition();
    window.addEventListener('resize', updateIframePosition);
    window.addEventListener('scroll', updateIframePosition);

    setInterval(() => {
      if (iframe && iframe.style.zIndex !== '2147483647') {
        iframe.style.zIndex = '2147483647';
      }
    }, 500);

    startPauseMonitor();
    return true;
  }

  function removeEmbed() {
    const iframe = document.getElementById(IFRAME_ID);
    if (iframe) {
      iframe.remove();
      console.log('[解析] iframe 已移除');
    }
    const marked = document.querySelectorAll(`[data-${CONTAINER_DATA_FLAG}]`);
    marked.forEach(el => {
      delete el.dataset[CONTAINER_DATA_FLAG];
    });
    stopPauseMonitor();
    parseActive = false;
    currentContainer = null;
  }

  function startParse() {
    if (parseActive) {
      console.log('[解析] 已在解析状态');
      return;
    }

    const canonical = extractCanonicalHtmlUrl(location.href);
    const parsePrefix = getCurrentParsePrefix();
    const targetUrl = parsePrefix + encodeURIComponent(canonical);
    console.log('[解析] 目标 URL:', targetUrl);

    const el = findBestPlayerElement();
    if (!el) {
      console.warn('[解析] 未找到合适的播放器元素');
      return;
    }

    const container = getSiteContainer(el);
    if (!container) {
      console.warn('[解析] 未找到有效容器');
      return;
    }

    currentContainer = container;
    if (ensureIframeIn(container, targetUrl)) {
      parseActive = true;
      console.log('[解析] 解析已启动');
      updateUI();
    }
  }

  function stopParse() {
    removeEmbed();
    console.log('[解析] 解析已停止');
    updateUI();
  }

  function toggleAutoParseSwitch() {
    const config = getConfig();
    config.enabled = !config.enabled;
    saveConfig(config);
    console.log('[解析] 自动解析开关:', config.enabled ? '开启' : '关闭');
    
    if (!config.enabled && parseActive) {
      stopParse();
    }
    
    if (config.enabled && isVideoPage() && !parseActive) {
      setTimeout(() => {
        startParse();
      }, 500);
    }
    
    updateUI();
  }

  function showInterfaceModal() {
    let panel = document.getElementById('vip-parser-interface-panel');
    if (panel) {
      panel.style.display = panel.style.display === 'none' ? 'flex' : 'none';
      return;
    }

    const uiContainer = document.getElementById('vip-parser-ui');
    const rect = uiContainer.getBoundingClientRect();

    panel = document.createElement('div');
    panel.id = 'vip-parser-interface-panel';
    panel.style.cssText = `
      position: fixed !important;
      top: ${rect.top}px !important;
      left: ${rect.right + 20}px !important;
      z-index: 2147483649 !important;
      display: flex !important;
      align-items: center !important;
      gap: 10px !important;
      background: rgba(255, 255, 255, 0.85) !important;
      backdrop-filter: blur(20px) !important;
      padding: 8px 16px !important;
      border-radius: 8px !important;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
      max-width: calc(100vw - ${rect.right + 40}px) !important;
      overflow-x: auto !important;
    `;

    const title = document.createElement('span');
    title.textContent = '选择接口:';
    title.style.cssText = `
      font-size: 14px !important;
      font-weight: 600 !important;
      color: #333 !important;
      white-space: nowrap !important;
    `;
    panel.appendChild(title);

    const config = getConfig();
    const currentIndex = config.selectedInterface || 0;

    const interfaceButtons = [];

    PARSE_INTERFACES.forEach((iface, index) => {
      const item = document.createElement('button');
      item.textContent = iface.name;
      item.dataset.interfaceIndex = index;
      item.style.cssText = `
        padding: 6px 14px !important;
        background: ${index === currentIndex ? '#1e90ff' : 'rgba(128, 128, 128, 0.2)'} !important;
        color: ${index === currentIndex ? '#fff' : '#333'} !important;
        border: none !important;
        border-radius: 6px !important;
        cursor: pointer !important;
        font-size: 13px !important;
        font-weight: 600 !important;
        transition: all 0.3s !important;
        white-space: nowrap !important;
      `;

      item.onmouseover = () => {
        const currentCfg = getConfig();
        const current = currentCfg.selectedInterface || 0;
        if (index !== current) {
          item.style.background = 'rgba(128, 128, 128, 0.3) !important';
        }
      };

      item.onmouseout = () => {
        const currentCfg = getConfig();
        const current = currentCfg.selectedInterface || 0;
        if (index !== current) {
          item.style.background = 'rgba(128, 128, 128, 0.2) !important';
        }
      };

      item.onclick = () => {
        const cfg = getConfig();
        cfg.selectedInterface = index;
        saveConfig(cfg);
        console.log('[解析] 切换接口:', iface.name);
        
        interfaceButtons.forEach((btn, i) => {
          if (i === index) {
            btn.style.cssText = `
              padding: 6px 14px !important;
              background: #1e90ff !important;
              color: #fff !important;
              border: none !important;
              border-radius: 6px !important;
              cursor: pointer !important;
              font-size: 13px !important;
              font-weight: 600 !important;
              transition: all 0.3s !important;
              white-space: nowrap !important;
            `;
          } else {
            btn.style.cssText = `
              padding: 6px 14px !important;
              background: rgba(128, 128, 128, 0.2) !important;
              color: #333 !important;
              border: none !important;
              border-radius: 6px !important;
              cursor: pointer !important;
              font-size: 13px !important;
              font-weight: 600 !important;
              transition: all 0.3s !important;
              white-space: nowrap !important;
            `;
          }
        });
        
        if (parseActive) {
          stopParse();
          setTimeout(() => {
            startParse();
          }, 300);
        }
      };

      interfaceButtons.push(item);
      panel.appendChild(item);
    });

    const closeBtn = document.createElement('button');
    closeBtn.textContent = '✕';
    closeBtn.style.cssText = `
      padding: 4px 10px !important;
      background: rgba(255, 0, 0, 0.1) !important;
      color: #f44336 !important;
      border: none !important;
      border-radius: 6px !important;
      cursor: pointer !important;
      font-size: 14px !important;
      font-weight: 700 !important;
      transition: all 0.3s !important;
      margin-left: 8px !important;
      white-space: nowrap !important;
    `;

    closeBtn.onmouseover = () => {
      closeBtn.style.background = 'rgba(255, 0, 0, 0.2) !important';
    };

    closeBtn.onmouseout = () => {
      closeBtn.style.background = 'rgba(255, 0, 0, 0.1) !important';
    };

    closeBtn.onclick = () => {
      panel.style.display = 'none';
    };

    panel.appendChild(closeBtn);
    document.body.appendChild(panel);

    document.addEventListener('click', function closePanelOutside(e) {
      const interfaceBtn = document.getElementById('vip-parser-interface');
      if (panel && !panel.contains(e.target) && !interfaceBtn.contains(e.target)) {
        panel.style.display = 'none';
      }
    });
  }

  function createUI() {
    const container = document.createElement('div');
    container.id = 'vip-parser-ui';
    container.style.cssText = `
      position: fixed !important;
      top: 10px !important;
      left: 10px !important;
      z-index: 2147483648 !important;
      display: flex !important;
      gap: 10px !important;
      align-items: center !important;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
    `;

    const toggleBtn = document.createElement('button');
    toggleBtn.id = 'vip-parser-toggle';
    
    const config = getConfig();
    toggleBtn.textContent = config.enabled ? '已开启' : '已关闭';
    toggleBtn.style.cssText = `
      padding: 10px 20px !important;
      background: rgba(128, 128, 128, 0.3) !important;
      border: none !important;
      border-radius: 6px !important;
      cursor: pointer !important;
      font-size: 14px !important;
      font-weight: 600 !important;
      color: #1e90ff !important;
      transition: all 0.3s ease !important;
      box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
      user-select: none !important;
      backdrop-filter: blur(10px) !important;
    `;

    toggleBtn.onmouseover = () => {
      toggleBtn.style.background = 'rgba(128, 128, 128, 0.4) !important';
      toggleBtn.style.boxShadow = '0 4px 12px rgba(30,144,255,0.3) !important';
    };

    toggleBtn.onmouseout = () => {
      toggleBtn.style.background = 'rgba(128, 128, 128, 0.3) !important';
      toggleBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15) !important';
    };

    toggleBtn.onclick = () => {
      toggleAutoParseSwitch();
    };

    const interfaceBtn = document.createElement('button');
    interfaceBtn.id = 'vip-parser-interface';
    interfaceBtn.textContent = '选择接口';
    interfaceBtn.style.cssText = `
      padding: 10px 20px !important;
      background: rgba(128, 128, 128, 0.3) !important;
      border: none !important;
      border-radius: 6px !important;
      cursor: pointer !important;
      font-size: 14px !important;
      font-weight: 600 !important;
      color: #1e90ff !important;
      transition: all 0.3s ease !important;
      box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
      user-select: none !important;
      backdrop-filter: blur(10px) !important;
    `;

    interfaceBtn.onmouseover = () => {
      interfaceBtn.style.background = 'rgba(128, 128, 128, 0.4) !important';
      interfaceBtn.style.boxShadow = '0 4px 12px rgba(30,144,255,0.3) !important';
    };

    interfaceBtn.onmouseout = () => {
      interfaceBtn.style.background = 'rgba(128, 128, 128, 0.3) !important';
      interfaceBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15) !important';
    };

    interfaceBtn.onclick = () => {
      showInterfaceModal();
    };

    container.appendChild(toggleBtn);
    container.appendChild(interfaceBtn);
    document.body.appendChild(container);

    console.log('[解析] UI 已创建');
  }

  function updateUI() {
    const toggleBtn = document.getElementById('vip-parser-toggle');
    if (toggleBtn) {
      const config = getConfig();
      toggleBtn.textContent = config.enabled ? '已开启' : '已关闭';
    }
  }

  function watchUrlChange() {
    urlCheckTimer = setInterval(() => {
      const currentUrl = location.href;
      if (currentUrl !== lastUrl) {
        console.log('[解析] URL 已变化:', currentUrl);
        lastUrl = currentUrl;
        
        if (parseActive) {
          removeEmbed();
        }

        setTimeout(() => {
          if (isVideoPage()) {
            console.log('[解析] 检测到播放页,准备自动解析');
            const config = getConfig();
            if (config.enabled) {
              startParse();
            }
          }
        }, 500);
      }
    }, 300);
  }

  function init() {
    console.log('[解析] 脚本启动, 当前站点:', currentSite);

    createUI();

    if (isVideoPage()) {
      console.log('[解析] 检测到播放页');
      const config = getConfig();
      if (config.enabled) {
        setTimeout(() => {
          startParse();
        }, 1000);
      }
    }

    watchUrlChange();
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    setTimeout(init, 500);
  }
})();