百度网盘破解SVIP&&倍速播放&&文稿字幕

百度网盘视频完全破解SVIP。电脑用户使用新版火狐或安插件设置UserAgent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0。iPad用户(我iOS15.7):安装Focus浏览器,安装插件,登录即为Svip。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name        百度网盘破解SVIP&&倍速播放&&文稿字幕
// @namespace   http://tampermonkey.net/
// @match       https://pan.baidu.com/
// @match       https://pan.baidu.com/*
// @exclude     https://pan.baidu.com/aipan/search
// @grant       unsafeWindow
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_addStyle
// @run-at      document-start
// @connect     pan.baidu.com
// @require     https://lib.baomitu.com/hls.js/latest/hls.js
// @version     1.5.4
// @license     MIT
// @author      Gwen
// @homepageURL https://greasyfork.org/zh-CN/scripts/468982-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E7%A0%B4%E8%A7%A3svip-%E5%80%8D%E9%80%9F%E6%92%AD%E6%94%BE-%E6%96%87%E7%A8%BF%E5%AD%97%E5%B9%95
// @description 百度网盘视频完全破解SVIP。电脑用户使用新版火狐或安插件设置UserAgent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0。iPad用户(我iOS15.7):安装Focus浏览器,安装插件,登录即为Svip。
// ==/UserScript==

(function() {

  GM_addStyle('#app{width:100%!important}.vp-toolsbar{display:none!important}.vp-header{min-width:0!important}.vp-personal-video-play{min-width:0}.vp-personal-home-layout__video{min-width:30vw;padding-top:10px!important;height:80vh!important}.vp-personal-home-layout{padding:0 20px!important}.vp-personal-home-layout .vp-aside{padding-top:0!important}.vp-tabs{min-width:10vw!important}')
  let originalAddEventListener = EventTarget.prototype.addEventListener;
  let hookAddEventListener = function(...args) {
    if (args[0] != "keydown" && args[0] != "keyup" && args[0] != "keypress") {
    	return originalAddEventListener.apply(this, args);
    }
  }

  EventTarget.prototype.addEventListener = hookAddEventListener;
  document.addEventListener = hookAddEventListener;
  document.documentElement.addEventListener = hookAddEventListener;

  var settings = {
    solve_subtitle: true, //处理文稿字幕
    subtitles: null,
    subtitle_enabled: false,
    subtitleAutoEnable: GM_getValue('subtitleAutoEnable', false), //是否自动开启字幕
    longPressRate: 2, //长按加速倍速,Safari受设备影响,我的设备最高只能2倍速
    lastPlaybackRate: GM_getValue('lastPlaybackRate', 1),
    lastCurrentTime: 0,
    lastVideoWidth: GM_getValue('lastVideoWidth', null),
    lastTabWidth: GM_getValue('lastTabWidth', null),
    resolution: null,
    failCountThreshold: 15, //视频加载几秒仍未加载插件则手动发送获取视频m3u8的请求
    path: null,
    isSvip: null,
    adToken: null,
    bdstoken: null,
    globalVideo: null,
    hls: null,
    histories: GM_getValue('histories', []),
    pdf: null,
    m3u8url: null,
    capturePath: null, // 要导出的pdf
    timePoints: null,
    captureHls: null,
  }
  unsafeWindow.fucksettings = settings
  if (location.href.indexOf('https://pan.baidu.com/disk/main') != -1) {
    hookRequest()
    unsafeWindow.locals.userInfo.vip_identity = '2'
        unsafeWindow.locals.userInfo.vip_level = '10'
        unsafeWindow.locals.userInfo.svip10_id = '000000'
    let fuckLocals = setInterval(() => {
      if (unsafeWindow?.locals?.userInfo) {
        clearInterval(fuckLocals);
        unsafeWindow.locals.userInfo.vip_identity = '21'
        unsafeWindow.locals.userInfo.vip_level = '10'
        unsafeWindow.locals.userInfo.svip10_id = '000000'
      }
    }, 100)
    function wait() {
      let center = document.head && document.body && document.querySelector('.wp-s-header__center')
      if (!center) {
        setTimeout(wait, 300)
      } else {
        initPlayHistory()
        let historyBtn = document.createElement('a')
        historyBtn.href = '#'
        historyBtn.innerText = '播放历史'
        historyBtn.onclick = e => {
          e.preventDefault()
          document.querySelector('.history-wrapper').style.display='block';
          loadHistories()
        }
        center.appendChild(historyBtn)
      }
    }
    wait()
    return;
  } else if (location.href.indexOf('/pfile') == -1) {
      hookRequest()
      setLocals()
    return
  }

  //兼容某些浏览器无GMapi
  function GM_setValue(key, value) {
    settings[key] = value
    if (typeof value === 'string') {
      localStorage.setItem(key, value);
    } else if (typeof value === 'number' || typeof value === 'boolean') {
      localStorage.setItem(key, value.toString());
    } else {
      localStorage.setItem(key, JSON.stringify(value));
    }
  }
  function GM_getValue(key, defaultValue = null) {
    const value = localStorage.getItem(key);
    if (value === null || value === undefined) {
      return defaultValue;
    }
    try {
      return JSON.parse(value);
    } catch (error) {
      alert(`Error parsing stored value for key '${key}': ${error}`);
      return defaultValue;
    }
  }
  function createElement(tag, clazz, attrs) {
    const elem = document.createElement(tag);
    elem.className = clazz;
    if (attrs) {
      for (let key in attrs) {
        elem[key] = attrs[key];
      }
    }
    return elem;
  }
  function throttle(fn, delay) {
    var ctx;
    var args;
    var previous = Date.now();
    var later = function () {
      fn.apply(ctx, args);
    };
    return function () {
      ctx = this;
      args = arguments;
      var now = Date.now();
      var diff = now - previous - delay;
      if (diff >= 0) {
        previous = now;
        setTimeout(later, delay);
      }
    };
  }

  var $msg = {success:console.log,error:console.log,info:console.log}
  let h0x00=setInterval(()=>{
    if(document&&document.head&&document.body) {
      clearInterval(h0x00)
      function useMessage(){function n(n){for(var o=10,e=0;e<f.length;e++){var t=f[e];if(n&&n===t)break;o+=t.clientHeight+20}return o}function o(o){for(var e=0;e<f.length;e++){if(f[e]===o){f.splice(e,1);break}}o.classList.add(a.hide),f.forEach(function(o){o.style.top=n(o)+"px"})}function e(e){function i(){p.removeEventListener("animationend",i),setTimeout(o,x||t.duration||3e3,p)}function s(){"0"===getComputedStyle(p).opacity&&(p.removeEventListener("transitionend",s),p.remove())}var d=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"info",x=arguments[2],p=r.createElement("div");p.className=a.box+" "+d,p.style.top=n()+"px",p.style.zIndex=c,p.innerHTML='\n    <span class="'+a.icon+'"></span>\n    <span class="'+a.text+'">'+e+"</span>\n    ",c++,f.push(p),r.body.appendChild(p),p.addEventListener("animationend",i),p.addEventListener("transitionend",s)}var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=document,i="__"+Math.random().toString(36).slice(2,7),a={box:"msg-box"+i,hide:"hide"+i,text:"msg-text"+i,icon:"msg-icon"+i},s=r.createElement("style");s.textContent=("\n  ."+a.box+", ."+a.icon+", ."+a.text+" {\n    padding: 0;\n    margin: 0;\n    box-sizing: border-box;\n  }\n  ."+a.box+" {\n    position: fixed;\n    top: 0;\n    left: 50%;\n    display: flex;\n    padding: 12px 16px;\n    border-radius: 2px;\n    background-color: #fff;\n    box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);\n    white-space: nowrap;\n    animation: "+a.box+"-move .4s;\n    transition: .4s all;\n    transform: translate3d(-50%, 0%, 0);\n    opacity: 1;\n    overflow: hidden;\n  }\n  ."+a.box+'::after {\n    content: "";\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: 100%;\n    width: 4px;\n  }\n  @keyframes '+a.box+"-move {\n    0% {\n      opacity: 0;\n      transform: translate3d(-50%, -100%, 0);\n    }\n    100% {\n      opacity: 1;\n      transform: translate3d(-50%, 0%, 0);\n    }\n  }\n  ."+a.box+"."+a.hide+" {\n    opacity: 0;\n    /* transform: translate3d(-50%, -100%, 0); */\n    transform: translate3d(-50%, -100%, 0) scale(0);\n  }\n  ."+a.icon+" {\n    display: inline-block;\n    width: 18px;\n    height: 18px;\n    border-radius: 50%;\n    overflow: hidden;\n    margin-right: 6px;\n    position: relative;\n  }\n  ."+a.text+" {\n    font-size: 14px;\n    line-height: 18px;\n    color: #555;\n  }\n  ."+a.icon+"::after,\n  ."+a.icon+'::before {\n    position: absolute;\n    content: "";\n    background-color: #fff;\n  }\n  .'+a.box+".info ."+a.icon+", ."+a.box+".info::after {\n    background-color: #1890ff;\n  }\n  ."+a.box+".success ."+a.icon+", ."+a.box+".success::after {\n    background-color: #52c41a;\n  }\n  ."+a.box+".warning ."+a.icon+", ."+a.box+".warning::after {\n    background-color: #faad14;\n  }\n  ."+a.box+".error ."+a.icon+", ."+a.box+".error::after {\n    background-color: #ff4d4f;\n  }\n  ."+a.box+".info ."+a.icon+"::after,\n  ."+a.box+".warning ."+a.icon+"::after {\n    top: 15%;\n    left: 50%;\n    margin-left: -1px;\n    width: 2px;\n    height: 2px;\n    border-radius: 50%;\n  }\n  ."+a.box+".info ."+a.icon+"::before,\n  ."+a.box+".warning ."+a.icon+"::before {\n    top: calc(15% + 4px);\n    left: 50%;\n    margin-left: -1px;\n    width: 2px;\n    height: 40%;\n  }\n  ."+a.box+".error ."+a.icon+"::after, \n  ."+a.box+".error ."+a.icon+"::before {\n    top: 20%;\n    left: 50%;\n    width: 2px;\n    height: 60%;\n    margin-left: -1px;\n    border-radius: 1px;\n  }\n  ."+a.box+".error ."+a.icon+"::after {\n    transform: rotate(-45deg);\n  }\n  ."+a.box+".error ."+a.icon+"::before {\n    transform: rotate(45deg);\n  }\n  ."+a.box+".success ."+a.icon+"::after {\n    box-sizing: content-box;\n    background-color: transparent;\n    border: 2px solid #fff;\n    border-left: 0;\n    border-top: 0;\n    height: 50%;\n    left: 35%;\n    top: 13%;\n    transform: rotate(45deg);\n    width: 20%;\n    transform-origin: center;\n  }\n  ").replace(/(\n|\t|\s)*/gi,"$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/gi,"$1").replace(/(\{|\}|\,|\:|\;)\s/gi,"$1"),r.head.appendChild(s);var c=t.zIndex||1e4,f=[];return{show:e,info:function(n){e(n,"info")},success:function(n){e(n,"success")},warning:function(n){e(n,"warning")},error:function(n){e(n,"error")}}}
      $msg=useMessage();
      $msg.success('脚本开始运行')
    }
  },100)

  //为解决某些我无法解决的视频加载错误或手机端插件各种bug的循环检错器
  var failCount = 0
  var failChecker = null
  //手动请求视频资源
  function fetchVideoM3U8() {
    settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
    let xhr = new XMLHttpRequest()
    let url = `https://pan.baidu.com/api/streaming?app_id=250528&clienttype=0&channel=chunlei&web=1&isplayer=1&check_blue=1&type=M3U8_AUTO_${settings.resolution?settings.resolution:'480'}&trans=&vip=0` +
          `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${settings.path}&jsToken=${unsafeWindow.jsToken}`
    xhr.open("GET", url)
    xhr.manual = true
    if (settings.adToken) {
      xhr.callback = function() {
        $msg.info('开始获取m3u8')
        fetchVideoM3U8()
      }
    }
    xhr.send()
  }
  function fetchResolution() {
    let xhr = new XMLHttpRequest()
    let url = `https://pan.baidu.com/api/filemetas?clienttype=0&app_id=250528&web=1&channel=chunlei`
    let body = `dlink=1&target=${encodeURIComponent('["' + settings.path + '"]')}`
    xhr.open("POST", url)
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.send(body)
  }
  function startFailChecker() {
    if (failChecker != null) {
      $msg.error('failChecker已在启动')
      return
    }
    failChecker = setInterval(() => {
      if (settings.globalVideo.readyState == 0) { //视频未成功加载
        failCount++
        if (failCount == settings.failCountThreshold) {
          $msg.error('视频未成功加载')
          clearInterval(failChecker)
          fetchVideoM3U8()
          failCount = 0
        }
      } else {
        $msg.success('视频成功加载')
        clearInterval(failChecker)
      }
    }, 1000)
  }

  function init() {
    let video = document.querySelector('video')
    if (!video) {
      console.log('still loading...')
      setTimeout(init, 400)
    } else if (video.id == 'vjs_video_3_html5_api') {
      video.remove()
      console.log('%removed beforeplay animation', 'color:blue')
      setTimeout(init, 400)
    } else {
      console.log('%cloaded!', 'color:red')
      $msg.success('加载成功')
      settings.globalVideo = video
      settings.path = new URLSearchParams(new URL(location.href).search).get('path');
      settings.isSvip = settings.isSvip
        || (document.querySelector('.vp-personal-userinfo__vip-icon')&&document.querySelector('.vp-personal-userinfo__vip-icon').src.indexOf('svip') != -1)
        || unsafeWindow?.locals?.is_svip
      fetchResolution()
      // if (settings.hls == null) {
      //   bindHls(video.src)
      // }

      video.parentElement.onselectstart = function(){return false;};
      video.autoplay = 'true'
      var scrubber = document.createElement("div");
      scrubber.style = 'text-align: center; width: 100%; z-index: 1000; color: white; font-weight: bold; text-shadow: black 0px 0px 10px;position: absolute;top: 50%;font-size: 30px;'
      var speedAlert = document.createElement('div')
      speedAlert.innerText = settings.longPressRate + '倍速播放中'
      speedAlert.style = 'text-align: center; width: 100%;position: absolute; top: 20px;z-index: 100; font-size: 30px;color: orange; text-shadow: black 0px 0px 20px;'
      speedAlert.style.display = 'none'
      video.parentElement.appendChild(scrubber);
      video.parentElement.appendChild(speedAlert)

      let scrubbing = false;
      let scrubStartX = 0;
      let scrubStartTime = 0;
      let deltaX = 0

      function updateScrubber() {
          var currentTime = video.currentTime;
          var duration = video.duration;
          scrubber.textContent = formatTime(currentTime) + " / " + formatTime(duration);
      }

      function formatTime(time) {
          var hours = Math.floor(time / 3600);
          var minutes = Math.floor((time % 3600) / 60);
          var seconds = Math.floor(time % 60);
          return (hours > 0 ? hours + ":" : "") + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
      }

      let isLongPress = false
      let longPressTimer;
      let longPressThreshold = 500; // milliseconds

      video.addEventListener("touchstart", function(event) {
          if (event.touches.length == 1) {
              isLongPress = false;
              scrubbing = true;
              scrubStartX = event.touches[0].pageX;
              scrubStartTime = video.currentTime;

              longPressTimer = setInterval(function() {
                  GM_setValue('lastPlaybackRate', video.playbackRate)
                  video.playbackRate = settings.longPressRate;
                  speedAlert.style.display = 'block'
                  isLongPress = true;
                  clearInterval(longPressTimer);
              }, longPressThreshold);
          }
      });

      video.addEventListener("touchmove", function(event) {
          if (scrubbing && event.touches.length == 1 && !isLongPress) {
              deltaX = event.touches[0].pageX - scrubStartX;
              var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
              if (scrubTime < 0) {
                  scrubTime = 0;
              } else if (scrubTime > video.duration) {
                  scrubTime = video.duration;
              }
              clearInterval(longPressTimer)
              scrubber.style.display = "block";
              scrubber.textContent = formatTime(scrubTime) + " / " + formatTime(video.duration);
          }
      });

      video.addEventListener("touchend", function(event) {
          if (scrubbing && event.touches.length == 0) {
              scrubbing = false;
              if (!isLongPress) {
                deltaX = event.changedTouches[0].pageX - scrubStartX;
                if (deltaX != 0) {
                  var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
                  if (scrubTime < 0) {
                      scrubTime = 0;
                  } else if (scrubTime > video.duration) {
                      scrubTime = video.duration;
                  }
                  video.currentTime = scrubTime;
                  scrubber.style.display = "none";
                }
              }

              clearInterval(longPressTimer);
              isLongPress = false
              speedAlert.style.display = 'none'
              video.playbackRate = settings.lastPlaybackRate;
          }
      });

      video.addEventListener("touchcancel", function(event) {
          if (scrubbing && event.touches.length == 0) {
              scrubbing = false;
              scrubber.style.display = "none";

              clearInterval(longPressTimer);
              isLongPress = false
              speedAlert.style.display = 'none'
              video.playbackRate = settings.lastPlaybackRate;
          }
      });

      //播放历史模块
      initPlayHistory()
      let playHistoryButton = document.createElement('li')
      playHistoryButton.className = 'vp-menu-item'
      playHistoryButton.innerHTML = `<a class="vp-link" href="#"><span>播放历史</span></a>`
      document.querySelector('.vp-menu').appendChild(playHistoryButton)
      playHistoryButton.onclick = e => {e.preventDefault();loadHistories();document.querySelector('.history-wrapper').style.display='block';}
      historyUpdateCount = 0

      //FIX: 替换自带progressBar修复手动使用Hls播放高清视频后的点击bug
      let progressBarHtml = `<div class="vjs-progress-control vjs-control vp-video-progress-control"><div tabindex="0" class="vjs-progress-holder vjs-slider vjs-slider-horizontal" role="slider" aria-valuenow="0.00" aria-valuemin="0" aria-valuemax="100" aria-label="进度条" aria-valuetext="2:01/-:-">
<div class="vjs-play-progress vjs-slider-bar" aria-hidden="true" style="width: 0%;"><div class="vjs-time-tooltip" aria-hidden="true" style="right: -18.5px;">0:00</div></div>
</div></div>`
      let progressParent = document.querySelector('.vjs-progress-control').parentElement
      progressParent.children[0].remove()
      progressParent.insertAdjacentHTML('afterbegin', progressBarHtml)
      let progressHolder = progressParent.children[0].children[0]
      let progressBar = progressHolder.children[0]
      let progressToolTip = progressBar.children[0]
      let subtitleElement = document.querySelector('.vp-video__subtitle-text-first')
      let subtitleTab = null
      let subtitleWrapper = null
      let subtitleContent = null
      let subtitleInitTimer = setInterval(() => {
        if (!subtitleTab) {
          const elements = document.querySelectorAll('.vp-tabs__header-item');
          elements.forEach(elem => {
            if (elem.innerText.trim() == '文稿') {
              console.log('已获取到文稿元素')
              subtitleTab = elem
            }
          })
        } else {
          if (subtitleTab.className.indexOf('active')) {
            if (!subtitleWrapper) {
              subtitleWrapper = document.querySelector('.ai-draft__wrap-list')
            } else {
              console.log('%c加载文稿列表', 'color:pink;')
              subtitleContent = subtitleWrapper.parentElement
              clearInterval(subtitleInitTimer)
            }
          }
        }
      }, 400)
      progressHolder.addEventListener('click', updateProgress);
      progressHolder.addEventListener('touchstart', updateProgress);

      subtitleElement.style.fontSize = '24px'
      subtitleElement.parentElement.style.height = 'fit-content'
      subtitleElement.parentElement.style.left = '50%'
      subtitleElement.parentElement.style.top = '80%'
      subtitleElement.parentElement.style.maxWidth = '70%'
      subtitleElement.parentElement.parentElement.style.position = 'relative'
      // function makeDraggableFunction(t,e,n,s){return function(l){var o=parseInt(t.style.left),i=parseInt(t.style.top),p=l.clientX-o,r=l.clientY-i;n&&n(),document.onmousemove=function(n){t.style.left=n.clientX-p+"px",t.style.top=n.clientY-r+"px",e||(parseInt(t.style.left)<=0&&(t.style.left="0px"),parseInt(t.style.top)<=0&&(t.style.top="0px"),parseInt(t.style.left)>=window.innerWidth-parseInt(t.style.width)&&(t.style.left=window.innerWidth-parseInt(t.style.width)+"px"),parseInt(t.style.top)>=window.innerHeight-parseInt(t.style.height)&&(t.style.top=window.innerHeight-parseInt(t.style.height)+"px"))},document.onmouseup=function(){document.onmousemove=null,document.onmouseup=null,s&&s()}}}
      function makeDraggableFunction(elem, allowMoveOut, exec, callback) {
  let handleMouseDown = function (event) {
    let offsetX = event.clientX - elem.getBoundingClientRect().left
    let offsetY = event.clientY - elem.getBoundingClientRect().top

    if (!!exec) {
      exec()
    }

    document.onmousemove = function (event) {
      let newLeft = event.clientX - offsetX
      let newTop = event.clientY - offsetY

      if (!allowMoveOut) {
        // 获取父元素的边界
        let parentRect = elem.parentElement.getBoundingClientRect()

        // 计算元素的右边界和底边界
        let rightBoundary = parentRect.right
        let bottomBoundary = parentRect.bottom

        // 调整新位置,确保元素不超出右边界和底边界
        newLeft = Math.min(Math.max(newLeft, parentRect.left), rightBoundary - elem.offsetWidth)
        newTop = Math.min(Math.max(newTop, parentRect.top), bottomBoundary - elem.offsetHeight)
      }

      elem.style.left = newLeft + 'px'
      elem.style.top = newTop + 'px'
    }

    document.onmouseup = function () {
      document.onmousemove = null
      document.onmouseup = null

      if (!!callback) {
        callback()
      }
    }
  }

  return handleMouseDown
}
      subtitleElement.parentElement.addEventListener('mousedown', makeDraggableFunction(subtitleElement.parentElement, false))

      // initDraftExport()

      //点击更新进度条位置
      function updateProgress(event) {
        var totalWidth = progressHolder.offsetWidth;
        var offsetX = 0;
        if (event.type === 'click') {
          offsetX = event.offsetX;
        } else if (event.type === 'touchstart') {
          offsetX = event.touches[0].clientX - progressHolder.getBoundingClientRect().left;
        }
        var percentage = (offsetX / totalWidth) * 100;
        progressBar.style.width = percentage + '%';
        let currentTime = (percentage / 100) * video.duration;
        progressToolTip.innerText = formatTime(currentTime)
        video.currentTime = currentTime;
      }
      progressHolder.addEventListener('touchmove', function(event) {
        event.preventDefault();
      });
      //寻找字幕
      function showSubtitle(subtitles, currentTime) {
        let left = 0;
        let right = subtitles.length - 1;
        while (left <= right) {
          let middle = Math.floor((left + right) / 2);
          let subtitle = subtitles[middle];
          if (currentTime >= subtitle.startTime && currentTime <= subtitle.endTime) {
            subtitleElement.innerHTML = subtitle.text;
            return;
          } else if (currentTime < subtitle.startTime) {
            right = middle - 1;
          } else {
            left = middle + 1;
          }
        }
        subtitleElement.innerHTML = '';
      }
      //video时间变化执行内容
      function videoTimeUpdate() {
        let currentTime = video.currentTime;
        // 更新自定义进度条的位置
        let percentage = (currentTime / video.duration) * 100;
        progressBar.style.width = percentage + '%';
        //如果开启了字幕,显示字幕
        if (settings.subtitle_enabled && settings.subtitles) {
          if (settings.subtitles) {
            showSubtitle(settings.subtitles, this.currentTime);
          } else {
            subtitleElement.innerText = '字幕正在加载中...'
          }
        }
        historyUpdateCount++
        if (historyUpdateCount == 25) {
          historyUpdateCount = 0
          let lastIdx = settings.path.lastIndexOf('/')
          let title = settings.path
          if (lastIdx != -1)
            title = settings.path.substring(lastIdx + 1)
          let history = {
            path: settings.path,
            title: title,
            timestamp: new Date().getTime(),
            duration: video.duration,
            current: Math.floor(currentTime)
          }
          updateHistory(history)
        }
        //破解Svip打开文稿后同步显示字幕位置
        if (!settings.isSvip && subtitleTab && subtitleTab.className.indexOf('active') != -1 && subtitleWrapper) {
          const paragraphs = subtitleWrapper.children
          let currentIndex = 0
          for (let i = 0; i < paragraphs.length; i++) {
            const paragraph = paragraphs[i]
            if (currentTime * 1000 >= paragraph.dataset.starttime) {
              currentIndex = i
            } else {
              break;
            }
          }
          //取消当前高亮
          try {
            subtitleWrapper.querySelectorAll('.ai-draft__p-sentence--fouce').forEach(node => node.className = 'ai-draft__p-sentence')
          } catch(err) {}
          const subtitles = settings.subtitles
          for (let i = currentIndex * 15; i < (currentIndex + 1) * 15 && i < settings.subtitles.length; i++) {
            if (currentTime > subtitles[i].startTime && currentTime < subtitles[i].endTime) {
              let paragraph = paragraphs[currentIndex].children[i % 15]
              paragraph.className = 'ai-draft__p-sentence ai-draft__p-sentence--fouce'
              paragraph.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
              break;
            }
          }
        }
      }
      video.addEventListener('timeupdate', throttle(videoTimeUpdate, 500));

      //前进后退倍速字幕
      //FIX: 修复替换进度条后键盘控制
      let longPressRightArrowTimer = null
      document.onkeydown = function(e) {
        if (e.key === 'ArrowLeft') {
          if (video.currentTime - 15 >= 0) {
            video.currentTime -= 15;
          } else {
            video.currentTime = 0;
          }
        } else if (e.key === 'ArrowRight') {
          if (video.currentTime + 15 < video.duration) {
            video.currentTime += 15;
          } else {
            video.currentTime = video.duration;
          }
        } else if (e.code === 'Space') {
          if (video.paused) {
            video.play()
          } else {
            video.pause()
          }
        }
      };

      let toolBox = document.createElement('div')
      toolBox.style='width:100%;margin:10px 0;display:flex;justify-content:space-between;flex-flow:row wrap;'
      toolBox.id = 'toolBox'
      let reloadBtn = createButton('重新加载', e => {
        fetchVideoM3U8()
      })
      let subtitleBtn = createButton(settings.subtitle_enabled ? '关闭字幕' : '开启字幕', e => {
        if (settings.subtitle_enabled) { //已经开启了,点击关闭
          subtitleBtn.textContent = '开启字幕'
          document.querySelector('.vp-video__subtitle-text').style.display = 'none'
          GM_setValue('subtitleAutoEnable', false)
        } else { //点击开启字幕
          subtitleBtn.textContent = '关闭字幕'
          document.querySelector('.vp-video__subtitle-text').style.display = 'block'
          if (!settings.subtitles) {
            $msg.info('开始加载字幕文件')
            document.querySelector('.vp-video__subtitle-text-first').innerText = '字幕正在加载中...'
            document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
          }
          if (!settings.subtitleAutoEnable && confirm('是否设置自动开启字幕?')) {
            GM_setValue('subtitleAutoEnable', true)
          }
        }
        settings.subtitle_enabled = !settings.subtitle_enabled
      })
      let rewindBtn = createButton('←15s', e => {
        if (video.currentTime - 15 >= 0) {
          video.currentTime -= 15;
        } else {
          video.currentTime = 0;
        }
      })
      let forwardBtn = createButton('15s→', e => {
        if (video.currentTime + 15 < video.duration) {
          video.currentTime += 15;
        } else {
          video.currentTime = video.duration;
        }
      })
      toolBox.appendChild(reloadBtn)
      toolBox.appendChild(subtitleBtn)
      toolBox.appendChild(rewindBtn)
      toolBox.appendChild(forwardBtn)
      let speedBox = document.createElement('select')
      speedBox.value = settings.lastPlaybackRate
      let speeds = [3, 2, 1.75, 1.5, 1, 0.75]
      speeds.forEach(speed => {
        const option = document.createElement('option');
        option.textContent = speed == 1 ? '正常' : (speed + 'X'); // 设置option的文本值
        option.value = speed;
        speedBox.appendChild(option);
      });
      speedBox.addEventListener('change', event => {
        const selectedSpeed = event.target.value;
        settings.globalVideo.playbackRate = selectedSpeed;
        GM_setValue('lastPlaybackRate', selectedSpeed)
      });
      toolBox.appendChild(speedBox)
      let resolutionBox = document.createElement('select')
      resolutionBox.id = 'resolution-box'
      resolutionBox.addEventListener('change', event => {
        const selectedResolution = event.target.value;
        if (selectedResolution == '1080') {
          alert('1080无法播放好像。。。')
          settings.resolution = '720'
        } else {
          settings.resolution = selectedResolution;
        }
        fetchVideoM3U8()
      })
      toolBox.appendChild(resolutionBox)
      //导出课件
      // let exportPdfBtn = createButton('导出课件', exportPdf1)
      // toolBox.appendChild(exportPdfBtn)
      video.parentElement.parentElement.parentElement.appendChild(toolBox)
      initDraftExport()
      if (settings.subtitleAutoEnable) {
        setTimeout(() => {
          subtitleBtn.click()
        }, 1500)
      }

      //FIX: 修改部分css修复宽度小时无法显示全的问题
      // setTimeout(() => {
      //   document.getElementById('app').style.width = '100%'
      //   document.querySelector('.vp-toolsbar').remove()
      //   document.querySelector('.vp-header').style.minWidth = '0'
      //   document.querySelector('.vp-personal-video-play').style.minWidth = '0'
      //   document.querySelector('.vp-personal-home-layout__video').style.minWidth = '30vw'
      //   document.querySelector('.vp-personal-home-layout__video').style.paddingTop = '10px'
      //   document.querySelector('.vp-personal-home-layout__video').style.height = '80vh'
      // }, 4000)
      // document.querySelector('.vp-personal-home-layout').style.padding = '0 20px'
      // document.querySelector('.vp-personal-home-layout .vp-aside').style.paddingTop = '0'
      // document.querySelector('.vp-tabs').style.minWidth = '10vw'
      if (settings.lastTabWidth) {
        document.querySelector('.vp-personal-home-layout .vp-aside').style.width = settings.lastTabWidth
      }
      function addSplitScreenAdjustment(element1Selector, element2Selector) {
        const element1 = document.querySelector(element1Selector);
        const element2 = document.querySelector(element2Selector);
        let scriptChanging = false;
        const handle = document.createElement('div');
        handle.style.width = '15px';
        handle.style.cursor = 'ew-resize';
        handle.style.backgroundColor = '#ccc';
        handle.style.margin = '0 5px';
        let startX, startWidth1, startWidth2;
        const handleMouseDown = (event) => {
          startX = event.clientX;
          startWidth1 = element1.offsetWidth;
          startWidth2 = element2.offsetWidth;
          scriptChanging = true
          document.addEventListener('mousemove', handleMouseMove);
          document.addEventListener('mouseup', handleMouseUp);
        };
        const handleMouseMove = throttle((event) => {
          const dx = event.clientX - startX;
          const newWidth1 = startWidth1 + dx;
          const newWidth2 = startWidth2 - dx;
          element1.style.width = newWidth1 + 'px';
          element2.style.width = newWidth2 + 'px';
        }, 100);
        const handleMouseUp = () => {
          document.removeEventListener('mousemove', handleMouseMove);
          document.removeEventListener('mouseup', handleMouseUp);
          scriptChanging = false
          GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
        };
        handle.addEventListener('mousedown', handleMouseDown);
        const handleTouchStart = (event) => {
          startX = event.touches[0].clientX;
          startWidth1 = element1.offsetWidth;
          startWidth2 = element2.offsetWidth;
          scriptChanging = true
          document.addEventListener('touchmove', handleTouchMove);
          document.addEventListener('touchend', handleTouchEnd);
        };
        const handleTouchMove = throttle((event) => {
          const dx = event.touches[0].clientX - startX;
          const newWidth1 = startWidth1 + dx;
          const newWidth2 = startWidth2 - dx;
          element1.style.width = newWidth1 + 'px';
          element2.style.width = newWidth2 + 'px';
        }, 100);
        const handleTouchEnd = () => {
          document.removeEventListener('touchmove', handleTouchMove);
          document.removeEventListener('touchend', handleTouchEnd);
          scriptChanging = false
          GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
        };
        handle.addEventListener('touchstart', handleTouchStart);
        element2.parentNode.insertBefore(handle, element2);
        //防止百度网盘自动调整他的宽度
        var observer = new MutationObserver(function(mutations) {
          mutations.forEach(function(mutation) {
            const oldStyle = mutation.oldValue;
            if (!scriptChanging) {
              observer.disconnect()
              setTimeout(() => {
                element2.style = oldStyle;
                observer.observe(element2, {attributes: true, attributeOldValue: true})
              }, 50)
            }
          });
        });
        observer.observe(element2, { attributes: true, attributeOldValue: true });
      }
      addSplitScreenAdjustment('main', '.vp-personal-home-layout .vp-aside')

      let rateOptions = document.querySelector('.vp-video__control-bar--playback-rates').children
      for (let i = 0; i < rateOptions.length; i++) {
        let option = rateOptions[i]
        if (option.classList[1] != 'is-svip-guide') {
          option.onclick = function(e) {
            e.stopPropagation()
            GM_setValue('lastPlaybackRate', Number.parseFloat(option.innerText.replace('X', '')))
            video.playbackRate = settings.lastPlaybackRate
          }
        }
      }

      //双击暂停
      let container = video.parentElement;
      let pauseThreshold = 500; // milliseconds
      let lastClickTime = 0;
      let pauseTimer;
      container.addEventListener("touchstart", function(event) {
        let currentTime = new Date().getTime();
        if (currentTime - lastClickTime < pauseThreshold) {
          clearTimeout(pauseTimer);
          if (video.paused) {
            video.play();
          } else {
            video.pause();
          }
        } else {
          lastClickTime = currentTime;
          pauseTimer = setTimeout(function() {
            lastClickTime = 0;
          }, pauseThreshold);
        }
      });

      //FIX: 替换自带全屏修复Focus浏览器全屏使用iOS默认播放器
      let vpVideo = document.querySelector('.vp-video')
      let controllBar = document.querySelector('.vp-video__control-bar--setup')
      controllBar.children[4].remove()
      controllBar.insertAdjacentHTML('beforeend', `<div class="vp-video__control-bar--button-group">
  <div class="vp-video__control-bar--button is-icon">
    <i class="u-icon-screen"></i></div></div>`)
      let fullScreenBtn = controllBar.children[4]
      let fullScreenIcon = fullScreenBtn.children[0].children[0]
      fullScreenBtn.onclick = e => {
        if (fullScreenIcon.className == 'u-icon-screen') {
          //点击全屏
          vpVideo.style.zIndex = '99999'
          vpVideo.style.position = 'fixed';
          vpVideo.style.top = '0';
          vpVideo.style.left = '0';
          fullScreenIcon.className = 'u-icon-exit-screen'
        } else {
          vpVideo.style.position = 'unset';
          fullScreenIcon.className = 'u-icon-screen'
        }
      }

      //准备完毕,开始检查视频状态
      startFailChecker()
    }
  }

  function createButton(textContent, callback) {
    let btn = document.createElement('div')
    btn.style = 'padding: 10px 10px; text-align: center; background: rgb(6, 167, 255); color: white;font-size: 14px;cursor:pointer'
    btn.textContent = textContent
    if (callback) {
      btn.onclick = e => {
        callback(e)
      }
    }
    return btn;
  }

  function formatPlayTime(timestamp) {
      let current = new Date().getTime()
      let gap = Math.floor((current - timestamp) / 1000)
      if (gap < 60) {
          return `${gap}秒前`
      } else if (gap < 60 * 60) {
          return `${Math.floor(gap/60)}分钟前`
      } else if (gap < 60 * 60 * 24) {
          return `${Math.floor(gap/60/60)}小时前`
      } else {
          return new Date(timestamp).toLocaleString()
      }
  }
  function initPlayHistory() {
      let style = document.createElement('style')
      style.textContent = '.history-wrapper{display:none;background-color:#fefefe;position:fixed;z-index:99999;top:40%;left:50%;transform:translate(-50%,-50%);padding:5px 10px 10px;border:1px solid #888;width:420px;}.history-list::-webkit-scrollbar{width:4px;}.history-list::-webkit-scrollbar-track{background-color:#f1f1f1;}.history-list::-webkit-scrollbar-thumb{background-color:#888888;}.history-list{min-height:100px;max-height:300px;overflow-y:auto;scrollbar-width:thin;}.history{padding:8px 4px;transition:.2s;cursor:pointer;border-bottom:1px dashed lightgray;}.history:hover{background-color:rgb(240,240,240);}.history-time{color:rgb(100,100,100);font-size:small;}.history-title{font-size:16px;}.history-progress-bar{width:280px;height:6px;background-color:#f1f1f1;border-radius:10px;overflow:hidden;display:inline-block;}.history-progress{width:0%;height:100%;background-color:#007bff;transition:width 0.3s ease-in-out;}.close{color:#aaa;float:right;font-size:28px;font-weight:bold;}.close:hover,.close:focus{color:black;text-decoration:none;cursor:pointer;}.history-end{color:rgb(150,150,150);text-align:center;font-size:small;}';
      document.head.append(style)
      let historyWrapper = document.createElement('div')
      historyWrapper.className = 'history-wrapper'
      historyWrapper.innerHTML = `<span style="float:left;margin-top: 5px;font-weight: bold;">播放历史(保留20条)</span>
          <span class="close" onclick="closeModal()">&times;</span>
          <div style="clear:both;"></div>
          <div class="history-list"></div>`
      historyWrapper.querySelector('.close').onclick = function(e) {
          historyWrapper.style.display = 'none'
      }
      document.body.append(historyWrapper)
      var existingHistoryIndex = settings.histories.findIndex(function(item) {
        return item.path === settings.path;
      });
      if (existingHistoryIndex != -1 && settings.globalVideo) {
        settings.globalVideo.currentTime = settings.histories[existingHistoryIndex].current
      }
      loadHistories()
  }
  function loadHistories() {
      let historyList = document.querySelector('.history-list')
      historyList.innerHTML = ''
      for (let i = settings.histories.length - 1; i >= 0; i--) {
        let history = settings.histories[i]
        let historyElem = document.createElement('div')
        historyElem.className = 'history'
        historyList.append(historyElem)
        historyElem.innerHTML = `<div class="history-time">${formatPlayTime(history.timestamp)}</div>
          <div class="history-title">${history.title}</div>
          <div class="history-progress-bar">
            <div class="history-progress" style="width:${100*history.current/history.duration}%"></div>
          </div>
          <span style="color:rgb(100,100,100);font-size:12px;">观看至${formatTime(history.current)}</span>`
        historyElem.setAttribute('path', history.path)
      }
      historyList.onclick = function(e) {
        const elem = event.target.closest('.history');
        if (elem) {
          const path = elem.getAttribute('path')
          if (confirm('跳转视频' + path + '?')) {
            location.href = 'https://pan.baidu.com/pfile/video?path=' + encodeURIComponent(path)
          }
        }
      }
      historyList.innerHTML += `<div class="history-end">—————— 已经到底了哦 ——————</div>`
  }
  function updateHistory(history) {
    const histories = settings.histories;
    var existingHistoryIndex = histories.findIndex(function(item) {
      return item.path === history.path;
    });
    if (existingHistoryIndex !== -1) {
      histories.splice(existingHistoryIndex, 1)[0];
      histories.push(history);
    } else {
      histories.push(history);
      if (histories.length > 20) {
        histories.splice(0, 1)
      }
    }
    GM_setValue('histories', histories);
  }

  function hookRequest() {
      var originOpen = XMLHttpRequest.prototype.open;
      XMLHttpRequest.prototype.open = function (method, url) {
        // console.log(`%c【${method}】:${url}`, 'color:blue;')
        if (url.indexOf('netdisk-subtitle') != -1 && settings.solve_subtitle) { //获取字幕信息
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              var blobData = this.response;
              var reader = new FileReader();
              reader.onloadend = function() {
                var textContent = reader.result;
                let arr = textContent.split('\n')
                setTimeout(() => {
                  let wrapper = document.querySelector('.ai-draft__wrap-list')
                  try {
                    document.querySelector('.ai-draft__wrap-content').style = 'padding-right:12px!important;'
                    document.querySelector('.ai-draft__wrap-content').contentEditable = true
                    document.querySelector('.ai-draft__filter').remove()
                    document.querySelector('.ai-draft__svip-guide').remove()
                  } catch(err) {
                  }
                  let subtitles = []
                  let paragraph
                  console.log('开始解析字幕')
                  for (let i = 1; i < arr.length / 4; i++) {
                    if (arr[i * 4] == '')
                      break;
                    let lines = [arr[i * 4], arr[i * 4 + 1], arr[i * 4 + 2]]
                    let timeParts = lines[1].split(' --> ');
                    let startTime = srtTimeToSeconds(timeParts[0].trim());
                    let endTime = srtTimeToSeconds(timeParts[1].trim());
                    if (!settings.isSvip && i >= 15 * 3 + 1) {
                      if (i % 15 == 1) {
                        paragraph = document.createElement('p')
                        paragraph.className = 'ai-draft__p-paragraph'
                        paragraph.setAttribute('data-starttime', startTime * 1000)
                      } else if (i % 15 == 0) {
                        wrapper.appendChild(paragraph)
                      }
                      let span = document.createElement('span')
                      span.className = 'ai-draft__p-sentence'
                      span.setAttribute('data-index', i - 1)
                      span.innerText = lines[2]
                      span.onclick = e => {
                        settings.globalVideo.currentTime = startTime
                      }
                      paragraph.appendChild(span)
                    }
                    subtitles.push({
                      index: i - 1,
                      startTime: startTime,
                      endTime: endTime,
                      text: lines[2]
                    })
                  }
                  settings.subtitles = subtitles
                }, 1000)
              };
              reader.readAsText(blobData);
            }
          });
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/api/loginStatus') != -1) { //伪造svip信息
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              let res = JSON.parse(this.responseText)
              settings.isSvip = res.login_info.vip_type == '21'
              res.login_info.vip_type = '21'
              res.login_info.vip_identity = '21'
              res.login_info.vip_level =  10
              res.login_info.vip_point = 99999
              res.login_info.svip10_id = '000000'
              res.login_info.username = 'sout(\'GwenCrack\')'
              settings.bdstoken = res.login_info.bdstoken
              Object.defineProperty(this, "responseText", {
                  writable: true,
              });
              this.responseText = JSON.stringify(res)
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/user/info') != -1) {
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              let res = {}
              if (this.responseType == 'json') {
                res = this.response
              } else {
                res = JSON.parse(this.responseText)
              }
              res.user_info.is_vip = 1
              res.user_info.is_svip = 1
              res.user_info.is_plus_buy =	1
              if (this.responseType == 'json') {
                Object.defineProperty(this, "responseText", {
                  writable: true,
                });
                this.responseText = JSON.stringify(res)
              } else {
                Object.defineProperty(this, "responseText", {
                  writable: true,
                });
                this.response = res
              }
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/membership/user') != -1) {
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              let res = {}
              if (this.responseType == 'json') {
                res = this.response
              } else {
                res = JSON.parse(this.responseText)
              }
              res.reminder = {
                "svip": {
                  "leftseconds": 999999999999,
                  "nextState": "normal"
                }
              }
              res.level_info = {
                "current_value": 12090,
                "current_level": 10,
                "history_value": 11830,
                "history_level": 10,
                "svip10_id": "000000",
                "last_manual_collection_time": 0
              }
              res.product_infos = [{
                "product_id": "",
                "start_time": 1685635199,
                "end_time": 2547483648,
                "buy_time": 0,
                "cluster": "vip",
                "detail_cluster": "svip",
                "auto_upgrade_to_svip": 0,
                "product_name": "svip2_nd",
                "status": 0,
                "function_num": 0,
                "buy_description": "",
                "product_description": "",
                "cur_svip_type": "month"
              }]
              res.current_product = {
                "cluster": "vip",
                "detail_cluster": "svip",
                "product_type": "vip2_1m_auto",
                "product_id": "12187135090581539740"
              }
              res.current_product_v2 = {
                "cluster": "vip",
                "detail_cluster": "svip",
                "product_type": "vip2_1m_auto",
                "product_id": "12187135090581539740"
              }
              if (this.responseType == 'json') {
                Object.defineProperty(this, "response", {
                  writable: true,
                });
                this.response = res
              } else {
                Object.defineProperty(this, "responseText", {
                  writable: true,
                });
                this.responseText = JSON.stringify(res)
              }
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/api/streaming') != -1 && url.indexOf('M3U8_SUBTITLE_SRT') == -1) { //获取视频m3u8接口
          let modifiedUrl = url.replace(/vip=2/, 'vip=0')
                  // .replace(/M3U8_.*?&/, 'M3U8_AUTO_1080&')
          if (settings.adToken) {
            modifiedUrl += ('&adToken=' + encodeURIComponent(settings.adToken))
            settings.adToken = null
          }
          originOpen.call(this, method, modifiedUrl, arguments[2]);
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              if (this.status != 200) {
                $msg.error('加载失败!!!!')
                return
              }
              if (this.responseText[0] == '{') {
                let res = JSON.parse(this.responseText)
                settings.adToken = res.adToken
                res.ltime = 0.001
                res.adTime = 0.001
                Object.defineProperty(this, "responseText", {
                  writable: true,
                });
                this.responseText = JSON.stringify(res)

                if (settings.isSvip) {
                  settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
                  let xhr = new XMLHttpRequest()
                  let url = `https://pan.baidu.com/api/streaming?app_id=250528&clienttype=0&channel=chunlei&web=1&isplayer=1&check_blue=1&type=M3U8_AUTO_${settings.resolution?settings.resolution:'480'}&trans=&vip=0` +
                        `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${encodeURIComponent(settings.path)}&jsToken=${unsafeWindow.jsToken}`
                  xhr.open("GET", url, false)
                  xhr.send()
                  this.responseText = xhr.responseText.replace(/https.*?\//, 'https://nv0.baidupcs.com/')
                } else if (this.callback) {
                  this.callback()
                }
              } else {
                let m3u8Content = this.responseText
                let blob = new Blob([this.responseText], { type: 'application/vnd.apple.mpegurl' });
                let url = URL.createObjectURL(blob);
                bindHls(url)
                settings.m3u8url = url
              }
            }
          })
        } else if (url.indexOf('/api/streaming') != -1 && url.indexOf('SUBTITLE_SRT') != -1) {
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              let res = this.responseText
              Object.defineProperty(this, "responseText", {
                  writable: true,
              });
              this.responseText = res.replace(/https:\/\/.*?\//, 'https://nv0.baidupcs.com/')
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/api/filemetas') != -1) {
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              let res = JSON.parse(this.responseText)
              if (res.info.length != 1)
                return
              let resolution = res.info[0].resolution
              console.log('分辨率'+ resolution)
              let resolutionOptions = []
              let match = false
              switch(resolution) {
                case 'width:1920,height:1080':
                  match = true
                  resolutionOptions.push('1080')
                case 'width:1280,height:720':
                  match = true
                  resolutionOptions.push('720')
                case 'width:720,height:480':
                  match = true
                  resolutionOptions.push('480')
                default:
                  resolutionOptions.push('360');
              }
              if (!match) {
                resolutionOptions = ['720', '480', '360']
              }
              console.log(resolutionOptions)
              let waitTimer = setInterval(() => {
                let box = document.getElementById('resolution-box')
                if (box) {
                  clearInterval(waitTimer)
                  box.innerHTML = ''
                  resolutionOptions.forEach(resolution => {
                    const option = document.createElement('option')
                    option.textContent = resolution + 'P'
                    option.value = resolution
                    box.appendChild(option)
                  })
                }
              }, 400)
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/msg/streaming') != -1 || url.indexOf('/share/streaming') != -1) {
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              if (this.responseText[0] != '{')
                return
              let res = JSON.parse(this.responseText)
              res.ltime = 0.000001
              res.adTime = 0.000001
              Object.defineProperty(this, 'responseText', {
                writable: true,
              })
              this.responseText = JSON.stringify(res)
            }
          })
          originOpen.apply(this, arguments);
        } else if (url.indexOf('/aitrans/ppt/get') != -1) {
          const parseTimeToSeconds = (timeStr) => {
            let [h, m, s] = timeStr.split(":");
            return parseInt(h)*60*60 + parseInt(m)*60 + parseInt(s);
          }
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              console.log("课件:" + this.responseText);
              let res = JSON.parse(this.responseText);
              console.log(res);
              let list = res.data.list;
              if (!list || list.length == 0) {
                $msg.error("没有pdf,等待生成或者别生成了");
                return;
              }
              const timePoints = [];
              for (let item of list) {
                for (let time of item.img_timestamp) {
                  console.log(time);
                  timePoints.push(parseTimeToSeconds(time))
                }
              }
              console.log(timePoints)
              settings.timePoints = timePoints;
              showPdf();
            }
          })
          originOpen.apply(this, arguments);
        } else {
          originOpen.apply(this, arguments);
        }

        /*
        else if (url.indexOf('/ppt/common') != -1) { // 查询ppt生成情况接口
          this.addEventListener('readystatechange', function() {
            if (this.readyState == 4) {
              console.log("课件:" + this.responseText)
              let res = JSON.parse(this.responseText)
              res.data.rights_info.residue_count = 10
              res.data.rights_info.vip_type = 4
              res.data.rights_info.strategy_flag = 1
              if (url.indexOf('method=apply') != -1) {
                res.data.apply_status = 200
              }
              Object.defineProperty(this, 'responseText', {
                writable: true
              })
              Object.defineProperty(this, 'response', {
                writable: true
              })
              this.response = JSON.stringify(res)
              this.responseText = JSON.stringify(res)
              console.log(res)
            }
          })
          originOpen.apply(this, arguments);
        }
        */
      }
    }

  function bindHls(url) {
    if (!Hls.isSupported()) {
      $msg.error('浏览器不支持播放')
      return
    }
    if (settings.hls) {
      try {
        settings.hls.destroy()
        settings.hls = null
      } catch(err) {
        console.error(err)
      }
    }
    settings.hls = new Hls({
      autoStartLoad: true,
      autoplay: true
    })
    let hls = settings.hls
    let video = settings.globalVideo
    let vpError = document.querySelector('.vp-error')
    if (vpError) {
      vpError.remove()
    }
    hls.on(Hls.Events.MEDIA_ATTACHED, function() {
      hls.loadSource(url);
    });
    hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
      console.log('上次加载到' + settings.lastCurrentTime)
      video.currentTime = settings.lastCurrentTime
      video.play();
      video.playbackRate = settings.lastPlaybackRate
      let checkDurationTimer = setInterval(() => {
        if (video.readyState > 0) {
          document.querySelector('.vp-video__control-bar--play-time-all>div').innerText = formatTime(video.duration)
          clearInterval(checkDurationTimer)
        }
      }, 100)
    });
    hls.attachMedia(video);
  }
  function srtTimeToSeconds(timeString) {
    var timeParts = timeString.split(':');
    var hours = parseInt(timeParts[0]);
    var minutes = parseInt(timeParts[1]);
    var secondsAndMilliseconds = timeParts[2].split('.');
    var seconds = parseInt(secondsAndMilliseconds[0]);
    var milliseconds = parseInt(secondsAndMilliseconds[1]);
    var totalSeconds = (hours * 3600) + (minutes * 60) + seconds + (milliseconds / 1000);
    return totalSeconds;
  }

  function formatTime(totalSeconds, requireMil = false) {
    var hours = Math.floor(totalSeconds / 3600);
    var minutes = Math.floor((totalSeconds % 3600) / 60);
    var seconds = Math.floor(totalSeconds % 60);
    var formattedTime = hours.toString().padStart(2, '0') + ':' +
                        minutes.toString().padStart(2, '0') + ':' +
                        seconds.toString().padStart(2, '0');
    if (requireMil) {
      formattedTime += ',' + (seconds % 1).toFixed(3).substring(2)
    }
    return formattedTime;
  }
  function copyToClipboard(txt){
    if (navigator.clipboard?.writeText)
      navigator.clipboard.writeText(txt)
    else {
      input = document.createElement('textarea')
      input.setAttribute('readonly', 'readonly')
      input.value = txt
      document.body.appendChild(input)
      input.select()
      if (document.execCommand('copy'))
        document.execCommand('copy')
      document.body.removeChild(input)
    }
  }
  function initDraftExport() {
    function getDefaultFilename(ext) {
      var videoNameElement = document.querySelector('div.vp-video-page-card span.is-playing.vp-video-page-card__video-name');
      if (videoNameElement) {
        var originalFilename = videoNameElement.innerText.trim();
        var newFilename = originalFilename.replace(/\.[^/.]+$/, '') + ext; // 去掉原始文件名的后缀,并添加新的后缀名
        return newFilename;
      }
      return 'subtitle' + ext;
    }
    let toolBox = document.getElementById('toolBox')
    // 创建复制字幕按钮
    function createDraftButton(id, textContent, left, callback) {
      const btn = document.createElement('button');
      btn.id = id;
      btn.innerText = textContent;
      // btn.style = `position:fixed;left:${left};bottom:3px;z-index:9999;padding:10px;background:#fff;border:1px solid #ccc;cursor:pointer;`
      btn.style = 'padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'
      // 复制字幕按钮点击事件处理函数
      btn.addEventListener('click', function() {
        if (!settings.subtitles) {
          $msg.info('视频文稿未加载,开始加载...')
          document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
          setTimeout(() => {
            document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
          }, 500)
          return
        }
        callback()
      });
      toolBox.append(btn)
    }
    createDraftButton('copySubtitleBtn', '复制字幕', '40px', function() {
      const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
      const subtitleText = [];
      for (let i = 0; i < subtitleElements.length; i++) {
        subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
      }
      copyToClipboard(subtitleText.join('\n\n')); // 将字幕数组以空行连接起来并返回
      $msg.success('字幕已复制')
    })
    createDraftButton('exportToDocBtn', '导出文稿doc', '120px', function() {
      $msg.info('正在导出文稿...')
      const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
      const subtitleText = [];
      for (let i = 0; i < subtitleElements.length; i++) {
        subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
      }
      const subtitle = subtitleText.join('\n\n'); // 获取字幕内容
      const filename = getDefaultFilename('.doc');
      var blob = new Blob([subtitle], {type: 'text/plain;charset=utf-8'});
      var downloadLink = document.createElement("a");
      downloadLink.href = URL.createObjectURL(blob);
      downloadLink.download = filename;
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);
      URL.revokeObjectURL(downloadLink.href);
      //saveAs(blob, filename); // 使用FileSaver.js保存文件
      $msg.success('导出成功')
    })
    createDraftButton('exportToSrtBtn', '导出字幕srt', '220px', function() {
      $msg.info('正在导出文稿...')
      const blobArray = [];
      settings.subtitles.forEach(function(subtitle) {
        const srtText =
          (subtitle.index + 1) + "\n" +
          formatTime(subtitle.startTime, true) + " --> " + formatTime(subtitle.endTime, true) + "\n" +
          subtitle.text + "\n\n";
        // console.log(srtText)
        const srtBlob = new Blob([srtText], { type: "text/plain;charset=utf-8" });
        blobArray.push(srtBlob);
      });
      var combinedBlob = new Blob(blobArray, { type: "text/plain;charset=utf-8" });
      var downloadLink = document.createElement("a");
      downloadLink.href = URL.createObjectURL(combinedBlob);
      downloadLink.download = getDefaultFilename('.srt');
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);
      URL.revokeObjectURL(downloadLink.href);
      //saveAs(combinedBlob, getDefaultFilename('.srt'));
      $msg.success('导出成功')
    })
  }

  hookRequest()
  init()
  function setLocals() {
    let localsTimer = setInterval(() => {
      if (!unsafeWindow.locals) return
      clearInterval(localsTimer)
      console.log('设置window.locas', unsafeWindow.locals)
      if (unsafeWindow.locals.userInfo) {
        unsafeWindow.locals.userInfo.vip_level = 8
        unsafeWindow.locals.userInfo.vip_identity = 21
        unsafeWindow.locals.userInfo.username = "GwenCrackヾ(-_-;)"
      } else if(unsafeWindow.locals.mset) {
        unsafeWindow.locals.mset({
          'is_vip': 1,
          'is_svip': 1,
          'vip_level': 8,
          'show_vip_ad': 0
        })
      } else {
        unsafeWindow.locals.vip_level = 8
        unsafeWindow.locals.is_vip = 1
        unsafeWindow.locals.is_svip = 1
        unsafeWindow.locals.is_evip = 0
        unsafeWindow.locals.show_vip_ad = 0
      }
    }, 100)
  }
  setLocals()
  let lastUrl = location.href
  setInterval(() => {
    if (lastUrl != location.href) {
      lastUrl = location.href
      console.log('%cURL变化为' + location.href, 'color:purple;')
      settings.path = new URLSearchParams(new URL(lastUrl).search).get('path');
      setTimeout(() => {
        $msg.info('重新加载字幕')
        settings.subtitles = null
        document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
        setTimeout(() => {
          document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
        }, 500)
      }, 4000)

    }
  }, 500)

  function showPdf() {
    document.querySelector('.vp-tabs__header').children[2].click()
    const doShow = () => {
      if (!settings.timePoints || settings.timePoints.length == 0) {
        console.log("正在加载课件时间点...");
        setTimeout(doShow, 500);
      } else {
        const video = document.createElement('video');
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        let aiCourse = document.querySelector(".vp-ai-course")
        let aiCourseTools = null;
        let logText = null;
        let reloadBtn = null;
        let exportBtn = null;
        let imageContainer = document.getElementById("export-image-container")
        // if (aiCourse && !aiCourse.querySelector('.vp-ai-course-tools')) {
        if (aiCourse) {
          aiCourse.innerHTML = ''
          aiCourseTools = createElement('div', 'vp-ai-course-tools', {style:'margin-bottom:5px;'})
          logText = createElement('span', '', {style:'margin-right:10px;font-size:16px;'})
          reloadBtn = createElement('button', '', {innerText: '重新加载', disabled: true, style: 'margin-right:10px;padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'});
          exportBtn = createElement('button', '', {innerText: '导出', disabled: true, style: 'padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'});
          reloadBtn.onclick = () => {
            if (!reloadBtn.disabled) {
              showPdf();
            }
          }
          let exportLock = false;
          exportBtn.onclick = () => {
            if (!exportBtn.disabled && !exportLock) {
              exportLock = true;
              $msg.info("正在写入pdf")
              if (settings.captureHls)
                settings.captureHls.destroy();
              settings.captureHls = null;
              // 导出pdf
              const imgs = imageContainer.querySelectorAll('img')
              let w = imgs[0].naturalWidth, h = imgs[0].naturalHeight
              let pdf = new jspdf.jsPDF('l', 'px', [w, h]);
              for (let i = 0; i < imgs.length; i++) {
                  let img = imgs[i]
                  pdf.addImage(img.src, 'JPEG', 0, 0, w, h)
                  pdf.addPage([img.naturalWidth, img.naturalHeight], 'l')
              }
              const targetPage = pdf.internal.getNumberOfPages()
              pdf.deletePage(targetPage)
              pdf.save(document.querySelector('.vp-personal-home-layout__video > .vp-toolsbar > .vp-toolsbar__title').title + '.pdf')
              $msg.success("导出成功")
              settings.timePoints = []
              exportLock = false;
            }
          }
          aiCourseTools.appendChild(logText)
          aiCourseTools.appendChild(reloadBtn)
          aiCourseTools.appendChild(exportBtn)
          aiCourse.append(aiCourseTools);
        }
        if (!imageContainer) {
          imageContainer = document.createElement('div')
          imageContainer.id = "export-image-container"
          imageContainer.style.overflowY = 'auto'
        } else {
          imageContainer.innerHTML = ''
        }
        aiCourse.appendChild(imageContainer)

        //const hls = new Hls();
        if (settings.captureHls) {
          settings.captureHls.destroy();
          settings.captureHls = null;
        }
        settings.captureHls = new Hls();
        const hls = settings.captureHls;
        video.volume = 0

        hls.on(Hls.Events.MEDIA_ATTACHED, function() {
            hls.loadSource(settings.m3u8url);
        });

        hls.on(Hls.Events.MANIFEST_PARSED, function() {
            video.play();
        });

        hls.on(Hls.Events.ERROR, function (event, data) {
          // console.log("HLS ERROR");
          // console.log(event, data)
          if (data.fatal && data.type == Hls.ErrorTypes.MEDIA_ERROR) {
            $msg.error("导出出现异常,尝试恢复")
            hls.recoverMediaError();
            logText.innerHTML = '<span style="color:red">导出出现异常,尝试恢复</span>'
          } else if (data.fatal) {
            $msg.error("导出失败,请重试")
            logText.innerHTML = '<span style="color:red">导出失败,请重试</span>'
            reloadBtn.disabled = false;
          }
        })

        video.oncanplay = function() {
          video.oncanplay = null;
          canvas.width = video.videoWidth;
          canvas.height = video.videoHeight;
          function captureScreenshot(timePoints, index) {
            if (index >= timePoints.length) {
              // 销毁视频对象
              settings.captureHls.destroy();
              settings.captureHls = null;
              logText.innerHTML = '加载完成';
              reloadBtn.disabled = false;
              exportBtn.disabled = false;
              return;
            }
            logText.innerHTML = `加载中(${index+1}/${timePoints.length})`
            const time = timePoints[index]
            video.pause();
            video.currentTime = time;
            video.onseeked = function() {
              video.onseeked = null;
              context.drawImage(video, 0, 0, canvas.width, canvas.height);
              const imgDataUrl = canvas.toDataURL('image/jpeg', 1);
              const imgWrapper = createElement('div', '', {style:'position:relative;width:100%;height:fit-content;'})
              const img = createElement('img', '', {src: imgDataUrl, style: 'width:100%;'});
              imgWrapper.append(img)
              imageContainer.appendChild(imgWrapper);
              imgWrapper.addEventListener('mouseenter', () => {
                const imgOverlay = createElement('div', 'img-index-text', {textContent:`${index+1}/${timePoints.length}`, style: "font-size:13px;position:absolute;top:0;left:0;background:black;color:white;padding:5px;border-radius:5px;"});
                imgWrapper.appendChild(imgOverlay);
              });
              imgWrapper.addEventListener('mouseleave', () => {
                const imgOverlay = imgWrapper.querySelector('.img-index-text');
                imgWrapper.removeChild(imgOverlay);
              });
              captureScreenshot(timePoints, index+1)
            };
          }
          captureScreenshot(settings.timePoints, 0)
        };
        hls.attachMedia(video);
      }
    }
    if (!unsafeWindow.jspdf) {
      let script = document.createElement('script')
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.1.1/jspdf.umd.min.js'
      document.head.appendChild(script)
      script.onload = doShow
      $msg.info("正在引入pdf所需js依赖")
    } else {
      doShow()
    }
  }

})()