Greasy Fork is available in English.

🔥【就是爽】【改进版】某度网盘 破解SVIP&&倍速播放&&文稿字幕&&样式&&解锁&&修复自动开启字幕,删广告样式等bug(360+chrome已测)....看网课专用!

直接上效果图,见下面........修复自动开启字幕和导出doc等bug,取消视频控制栏的阴影等等 (20240825修正到1.3.7→1.6,修正错误:开启脚本后无法加载字幕)

  1. // ==UserScript==
  2. // @name 🔥【就是爽】【改进版】某度网盘 破解SVIP&&倍速播放&&文稿字幕&&样式&&解锁&&修复自动开启字幕,删广告样式等bug(360+chrome已测)....看网课专用!
  3. // @namespace http://tampermonkey.net/
  4. // @match https://pan.baidu.com/
  5. // @match https://pan.baidu.com/*
  6. // @exclude https://pan.baidu.com/aipan/search
  7. // @grant unsafeWindow
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_addStyle
  11. // @run-at document-start
  12. // @connect pan.baidu.com
  13. // @require https://lib.baomitu.com/hls.js/latest/hls.js
  14. // @version 1.6
  15. // @description 直接上效果图,见下面........修复自动开启字幕和导出doc等bug,取消视频控制栏的阴影等等 (20240825修正到1.3.7→1.6,修正错误:开启脚本后无法加载字幕)
  16. // @license none
  17. // @author Gwen
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. //GM_addStyle('#app{width:100%!important}.vp-header{min-width:0!important}.video-js .vjs-control-bar {height: auto;display: block;position: relative; top:96.5%}.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}')
  22.  
  23. let originalAddEventListener = EventTarget.prototype.addEventListener;
  24. let hookAddEventListener = function(...args) {
  25. if (args[0] != "keydown" && args[0] != "keyup" && args[0] != "keypress") {
  26. return originalAddEventListener.apply(this, args);
  27. }
  28. }
  29. //EventTarget.prototype.addEventListener = hookAddEventListener; //向文山修改处 解决了快捷键失效的问题
  30. //document.addEventListener = hookAddEventListener; //向文山修改处 解决了快捷键失效的问题
  31. document.documentElement.addEventListener = hookAddEventListener;
  32.  
  33. var settings = {
  34. solve_subtitle: true, //处理文稿字幕
  35. subtitles: null,
  36. subtitle_enabled: false,
  37. subtitleAutoEnable: GM_getValue('subtitleAutoEnable', true), //是否自动开启字幕 向文山修改处20240325 设为true和false,都一样
  38. longPressRate: 2, //长按加速倍速,Safari受设备影响,我的设备最高只能2倍速
  39. lastPlaybackRate: GM_getValue('lastPlaybackRate', 1),
  40. lastCurrentTime: 0,
  41. lastVideoWidth: GM_getValue('lastVideoWidth', null),
  42. lastTabWidth: GM_getValue('lastTabWidth', null),
  43. resolution: null,
  44. failCountThreshold: 15, //视频加载几秒仍未加载插件则手动发送获取视频m3u8的请求
  45. path: null,
  46. isSvip: null,
  47. adToken: null,
  48. bdstoken: null,
  49. globalVideo: null,
  50. hls: null,
  51. histories: GM_getValue('histories', []),
  52. }
  53. if (location.href.indexOf('https://pan.baidu.com/disk/main') != -1) {
  54. function wait() {
  55. let center = document.head && document.body && document.querySelector('.wp-s-header__center')
  56. if (!center) {
  57. setTimeout(wait, 300)
  58. } else {
  59. initPlayHistory()
  60. let historyBtn = document.createElement('a')
  61. historyBtn.href = '#'
  62. historyBtn.innerText = '播放历史'
  63. historyBtn.onclick = e => {
  64. e.preventDefault()
  65. document.querySelector('.history-wrapper').style.display='block';
  66. loadHistories()
  67. }
  68. center.appendChild(historyBtn)
  69. }
  70. }
  71. wait()
  72. return;
  73. } else if (location.href.indexOf('/pfile') == -1) {
  74. hookRequest()
  75. let localsTimer = setInterval(() => {
  76. if (!unsafeWindow.locals) return
  77. clearInterval(localsTimer)
  78. console.log('设置window.locas', unsafeWindow.locals)
  79. let originalSet = unsafeWindow.locals.set
  80. unsafeWindow.locals.set = function(n, t) {
  81. console.log('%c[hook]' + n + ': ' + t, 'color:blue;')
  82. if (['is_vip', 'is_svip'].indexOf(n) != -1) {
  83. t = 1
  84. } else if (n == 'vip_level') {
  85. t = 10
  86. } else if (n == 'v10_id') {
  87. t = '666666'
  88. }
  89. console.log(arguments)
  90. originalSet.apply(this, [n, t])
  91. }
  92. if (unsafeWindow.locals.userInfo) {
  93. unsafeWindow.locals.userInfo.vip_level = 8
  94. unsafeWindow.locals.userInfo.vip_identity = 21
  95. unsafeWindow.locals.userInfo.username = "GwenCrackヾ(-_-;)"
  96. } else if(unsafeWindow.locals.mset) {
  97. unsafeWindow.locals.mset({
  98. 'is_vip': 1,
  99. 'is_svip': 1,
  100. 'vip_level': 10,
  101. 'show_vip_ad': 0
  102. })
  103. } else {
  104. unsafeWindow.locals.vip_level = 8
  105. unsafeWindow.locals.is_vip = 1
  106. unsafeWindow.locals.is_svip = 1
  107. unsafeWindow.locals.is_evip = 0
  108. unsafeWindow.locals.show_vip_ad = 0
  109. }
  110. }, 10)
  111. return
  112. }
  113.  
  114. //兼容某些浏览器无GMapi
  115. function GM_setValue(key, value) {
  116. settings[key] = value
  117. if (typeof value === 'string') {
  118. localStorage.setItem(key, value);
  119. } else if (typeof value === 'number' || typeof value === 'boolean') {
  120. localStorage.setItem(key, value.toString());
  121. } else {
  122. localStorage.setItem(key, JSON.stringify(value));
  123. }
  124. }
  125. function GM_getValue(key, defaultValue = null) {
  126. const value = localStorage.getItem(key);
  127. if (value === null || value === undefined) {
  128. return defaultValue;
  129. }
  130. try {
  131. return JSON.parse(value);
  132. } catch (error) {
  133. alert(`Error parsing stored value for key '${key}': ${error}`);
  134. return defaultValue;
  135. }
  136. }
  137. function throttle(fn, delay) {
  138. var ctx;
  139. var args;
  140. var previous = Date.now();
  141. var later = function () {
  142. fn.apply(ctx, args);
  143. };
  144. return function () {
  145. ctx = this;
  146. args = arguments;
  147. var now = Date.now();
  148. var diff = now - previous - delay;
  149. if (diff >= 0) {
  150. previous = now;
  151. setTimeout(later, delay);
  152. }
  153. };
  154. }
  155.  
  156. var $msg = {success:console.log,error:console.log,info:console.log}
  157. let h0x00=setInterval(()=>{
  158. if(document&&document.head&&document.body) {
  159. clearInterval(h0x00)
  160. 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")}}}
  161. $msg=useMessage();
  162. $msg.success('脚本开始运行')
  163. }
  164. },100)
  165.  
  166. //为解决某些我无法解决的视频加载错误或手机端插件各种bug的循环检错器
  167. var failCount = 0
  168. var failChecker = null
  169. //手动请求视频资源
  170. function fetchVideoM3U8() {
  171. settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
  172. let xhr = new XMLHttpRequest()
  173. 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` +
  174. `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${settings.path}&jsToken=${unsafeWindow.jsToken}`
  175. xhr.open("GET", url)
  176. xhr.manual = true
  177. if (settings.adToken) {
  178. xhr.callback = function() {
  179. $msg.info('开始获取m3u8')
  180. fetchVideoM3U8()
  181. }
  182. }
  183. xhr.send()
  184. }
  185. function fetchResolution() {
  186. let xhr = new XMLHttpRequest()
  187. let url = `https://pan.baidu.com/api/filemetas?clienttype=0&app_id=250528&web=1&channel=chunlei`
  188. let body = `dlink=1&target=${encodeURIComponent('["' + settings.path + '"]')}`
  189. xhr.open("POST", url)
  190. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
  191. xhr.send(body)
  192. }
  193. function startFailChecker() {
  194. if (failChecker != null) {
  195. $msg.error('failChecker已在启动')
  196. return
  197. }
  198. failChecker = setInterval(() => {
  199. if (settings.globalVideo.readyState == 0) { //视频未成功加载
  200. failCount++
  201. if (failCount == settings.failCountThreshold) {
  202. $msg.error('视频未成功加载')
  203. clearInterval(failChecker)
  204. fetchVideoM3U8()
  205. failCount = 0
  206. }
  207. } else {
  208. $msg.success('视频成功加载')
  209. clearInterval(failChecker)
  210. }
  211. }, 1000)
  212. }
  213.  
  214. function init() {
  215. let video = document.querySelector('video')
  216. if (!video) {
  217. console.log('still loading...')
  218. setTimeout(init, 400)
  219. } else if (video.id == 'vjs_video_3_html5_api') {
  220. video.remove()
  221. console.log('%removed beforeplay animation', 'color:blue')
  222. setTimeout(init, 400)
  223. } else {
  224. console.log('%cloaded!', 'color:red')
  225. $msg.success('加载成功')
  226. settings.globalVideo = video
  227. settings.path = new URLSearchParams(new URL(location.href).search).get('path');
  228. settings.isSvip = settings.isSvip
  229. || (document.querySelector('.vp-personal-userinfo__vip-icon')&&document.querySelector('.vp-personal-userinfo__vip-icon').src.indexOf('svip') != -1)
  230. || unsafeWindow?.locals?.is_svip
  231. fetchResolution()
  232. // if (settings.hls == null) {
  233. // bindHls(video.src)
  234. // }
  235.  
  236. video.parentElement.onselectstart = function(){return false;};
  237. video.autoplay = 'true'
  238. var scrubber = document.createElement("div");
  239. 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;'
  240. var speedAlert = document.createElement('div')
  241. speedAlert.innerText = settings.longPressRate + '倍速播放中'
  242. 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;'
  243. speedAlert.style.display = 'none'
  244. video.parentElement.appendChild(scrubber);
  245. video.parentElement.appendChild(speedAlert)
  246.  
  247. let scrubbing = false;
  248. let scrubStartX = 0;
  249. let scrubStartTime = 0;
  250. let deltaX = 0
  251.  
  252. function updateScrubber() {
  253. var currentTime = video.currentTime;
  254. var duration = video.duration;
  255. scrubber.textContent = formatTime(currentTime) + " / " + formatTime(duration);
  256. }
  257.  
  258. function formatTime(time) {
  259. var hours = Math.floor(time / 3600);
  260. var minutes = Math.floor((time % 3600) / 60);
  261. var seconds = Math.floor(time % 60);
  262. return (hours > 0 ? hours + ":" : "") + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
  263. }
  264.  
  265. let isLongPress = false
  266. let longPressTimer;
  267. let longPressThreshold = 500; // milliseconds
  268.  
  269. video.addEventListener("touchstart", function(event) {
  270. if (event.touches.length == 1) {
  271. isLongPress = false;
  272. scrubbing = true;
  273. scrubStartX = event.touches[0].pageX;
  274. scrubStartTime = video.currentTime;
  275.  
  276. longPressTimer = setInterval(function() {
  277. GM_setValue('lastPlaybackRate', video.playbackRate)
  278. video.playbackRate = settings.longPressRate;
  279. speedAlert.style.display = 'block'
  280. isLongPress = true;
  281. clearInterval(longPressTimer);
  282. }, longPressThreshold);
  283. }
  284. });
  285.  
  286. video.addEventListener("touchmove", function(event) {
  287. if (scrubbing && event.touches.length == 1 && !isLongPress) {
  288. deltaX = event.touches[0].pageX - scrubStartX;
  289. var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
  290. if (scrubTime < 0) {
  291. scrubTime = 0;
  292. } else if (scrubTime > video.duration) {
  293. scrubTime = video.duration;
  294. }
  295. clearInterval(longPressTimer)
  296. scrubber.style.display = "block";
  297. scrubber.textContent = formatTime(scrubTime) + " / " + formatTime(video.duration);
  298. }
  299. });
  300.  
  301. video.addEventListener("touchend", function(event) {
  302. if (scrubbing && event.touches.length == 0) {
  303. scrubbing = false;
  304. if (!isLongPress) {
  305. deltaX = event.changedTouches[0].pageX - scrubStartX;
  306. if (deltaX != 0) {
  307. var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
  308. if (scrubTime < 0) {
  309. scrubTime = 0;
  310. } else if (scrubTime > video.duration) {
  311. scrubTime = video.duration;
  312. }
  313. video.currentTime = scrubTime;
  314. scrubber.style.display = "none";
  315. }
  316. }
  317.  
  318. clearInterval(longPressTimer);
  319. isLongPress = false
  320. speedAlert.style.display = 'none'
  321. video.playbackRate = settings.lastPlaybackRate;
  322. }
  323. });
  324.  
  325. video.addEventListener("touchcancel", function(event) {
  326. if (scrubbing && event.touches.length == 0) {
  327. scrubbing = false;
  328. scrubber.style.display = "none";
  329.  
  330. clearInterval(longPressTimer);
  331. isLongPress = false
  332. speedAlert.style.display = 'none'
  333. video.playbackRate = settings.lastPlaybackRate;
  334. }
  335. });
  336.  
  337. //播放历史模块
  338. initPlayHistory()
  339. let playHistoryButton = document.createElement('li')
  340. playHistoryButton.className = 'vp-menu-item'
  341. playHistoryButton.innerHTML = `<a class="vp-link" href="#"><span>播放历史</span></a>`
  342. document.querySelector('.vp-menu').appendChild(playHistoryButton)
  343. playHistoryButton.onclick = e => {e.preventDefault();loadHistories();document.querySelector('.history-wrapper').style.display='block';}
  344. historyUpdateCount = 0
  345.  
  346. //FIX: 替换自带progressBar修复手动使用Hls播放高清视频后的点击bug
  347. 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/-:-">
  348. <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>
  349. </div></div>`
  350. let progressParent = document.querySelector('.vjs-progress-control').parentElement
  351. progressParent.children[0].remove()
  352. progressParent.insertAdjacentHTML('afterbegin', progressBarHtml)
  353. let progressHolder = progressParent.children[0].children[0]
  354. let progressBar = progressHolder.children[0]
  355. let progressToolTip = progressBar.children[0]
  356. let subtitleElement = document.querySelector('.vp-video__subtitle-text-first')
  357. let subtitleTab = null
  358. let subtitleWrapper = null
  359. let subtitleContent = null
  360. let subtitleInitTimer = setInterval(() => {
  361. if (!subtitleTab) {
  362. const elements = document.querySelectorAll('.vp-tabs__header-item');
  363. elements.forEach(elem => {
  364. if (elem.innerText.trim() == '文稿') {
  365. console.log('已获取到文稿元素')
  366. subtitleTab = elem
  367. }
  368. })
  369. } else {
  370. if (subtitleTab.className.indexOf('active')) {
  371. if (!subtitleWrapper) {
  372. subtitleWrapper = document.querySelector('.ai-draft__wrap-list')
  373. } else {
  374. console.log('%c加载文稿列表', 'color:pink;')
  375. subtitleContent = subtitleWrapper.parentElement
  376. clearInterval(subtitleInitTimer)
  377. }
  378. }
  379. }
  380. }, 400)
  381.  
  382. progressHolder.addEventListener('click', updateProgress);
  383. progressHolder.addEventListener('touchstart', updateProgress);
  384. subtitleElement.style.fontSize = '20px' // 向文山修改处 将24改回
  385. // initDraftExport()
  386.  
  387. //点击更新进度条位置
  388. function updateProgress(event) {
  389. var totalWidth = progressHolder.offsetWidth;
  390. var offsetX = 0;
  391. if (event.type === 'click') {
  392. offsetX = event.offsetX;
  393. } else if (event.type === 'touchstart') {
  394. offsetX = event.touches[0].clientX - progressHolder.getBoundingClientRect().left;
  395. }
  396. var percentage = (offsetX / totalWidth) * 100;
  397. progressBar.style.width = percentage + '%';
  398. let currentTime = (percentage / 100) * video.duration;
  399. progressToolTip.innerText = formatTime(currentTime)
  400. video.currentTime = currentTime;
  401. }
  402. progressHolder.addEventListener('touchmove', function(event) {
  403. event.preventDefault();
  404. });
  405. //寻找字幕
  406. function showSubtitle(subtitles, currentTime) {
  407. let left = 0;
  408. let right = subtitles.length - 1;
  409. while (left <= right) {
  410. let middle = Math.floor((left + right) / 2);
  411. let subtitle = subtitles[middle];
  412. if (currentTime >= subtitle.startTime && currentTime <= subtitle.endTime) {
  413. subtitleElement.innerHTML = subtitle.text;
  414. return;
  415. } else if (currentTime < subtitle.startTime) {
  416. right = middle - 1;
  417. } else {
  418. left = middle + 1;
  419. }
  420. }
  421. subtitleElement.innerHTML = '';
  422. }
  423. //video时间变化执行内容
  424. function videoTimeUpdate() {
  425. let currentTime = video.currentTime;
  426. // 更新自定义进度条的位置
  427. let percentage = (currentTime / video.duration) * 100;
  428. progressBar.style.width = percentage + '%';
  429. //如果开启了字幕,显示字幕
  430. if (settings.subtitle_enabled && settings.subtitles) {
  431. if (settings.subtitles) {
  432. showSubtitle(settings.subtitles, this.currentTime);
  433. } else {
  434. subtitleElement.innerText = '字幕正在加载中...'
  435. }
  436. }
  437. historyUpdateCount++
  438. if (historyUpdateCount == 25) {
  439. historyUpdateCount = 0
  440. let lastIdx = settings.path.lastIndexOf('/')
  441. let title = settings.path
  442. if (lastIdx != -1)
  443. title = settings.path.substring(lastIdx + 1)
  444. let history = {
  445. path: settings.path,
  446. title: title,
  447. timestamp: new Date().getTime(),
  448. duration: video.duration,
  449. current: Math.floor(currentTime)
  450. }
  451. updateHistory(history)
  452. }
  453. //破解Svip打开文稿后同步显示字幕位置
  454. if (!settings.isSvip && subtitleTab && subtitleTab.className.indexOf('active') != -1 && subtitleWrapper) {
  455. const paragraphs = subtitleWrapper.children
  456. let currentIndex = 0
  457. for (let i = 0; i < paragraphs.length; i++) {
  458. const paragraph = paragraphs[i]
  459. if (currentTime * 1000 >= paragraph.dataset.starttime) {
  460. currentIndex = i
  461. } else {
  462. break;
  463. }
  464. }
  465. //取消当前高亮
  466. try {
  467. subtitleWrapper.querySelectorAll('.ai-draft__p-sentence--fouce').forEach(node => node.className = 'ai-draft__p-sentence')
  468. } catch(err) {}
  469. const subtitles = settings.subtitles
  470. for (let i = currentIndex * 15; i < (currentIndex + 1) * 15 && i < settings.subtitles.length; i++) {
  471. if (currentTime > subtitles[i].startTime && currentTime < subtitles[i].endTime) {
  472. let paragraph = paragraphs[currentIndex].children[i % 15]
  473. paragraph.className = 'ai-draft__p-sentence ai-draft__p-sentence--fouce'
  474. paragraph.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
  475. break;
  476. }
  477. }
  478. }
  479. }
  480. video.addEventListener('timeupdate', throttle(videoTimeUpdate, 500));
  481.  
  482. //前进后退倍速字幕
  483. //FIX: 修复替换进度条后键盘控制
  484. let longPressRightArrowTimer = null
  485. document.onkeydown = function(e) {
  486. if (e.key === 'ArrowLeft') {
  487. if (video.currentTime - 15 >= 0) {
  488. video.currentTime -= 15;
  489. } else {
  490. video.currentTime = 0;
  491. }
  492. } else if (e.key === 'ArrowRight') {
  493. if (video.currentTime + 15 < video.duration) {
  494. video.currentTime += 15;
  495. } else {
  496. video.currentTime = video.duration;
  497. }
  498. } else if (e.code === 'Space') {
  499. if (video.paused) {
  500. video.play()
  501. } else {
  502. video.pause()
  503. }
  504. }
  505. };
  506.  
  507. let toolBox = document.createElement('div')
  508. toolBox.style='width:100%;margin:10px 0;display:flex;justify-content:space-between;flex-flow:row wrap;'
  509. toolBox.id = 'toolBox'
  510. let reloadBtn = createButton('重新加载', e => {
  511. fetchVideoM3U8()
  512. })
  513. let subtitleBtn = createButton(settings.subtitle_enabled ? '关闭字幕' : '开启字幕', e => {
  514. if (settings.subtitle_enabled) { //已经开启了,点击关闭
  515. subtitleBtn.textContent = '开启字幕'
  516. document.querySelector('.vp-video__subtitle-text').style.display = 'none'
  517. GM_setValue('subtitleAutoEnable', false)
  518. } else { //点击开启字幕
  519. subtitleBtn.textContent = '关闭字幕'
  520. //document.querySelector('.vp-video__subtitle-text').style = 'display: block;opacity: 0.8;pointer-events:none' //这是1.37版 //向文山备注处
  521. setTimeout(() => { // 向文山修改处20240325 可实现凡是之前上一次设置过开启字幕,下一次加载视频按钮会显示"关闭字幕",就会自动点击字幕分栏,设置了setTimeout才会反复检查是否切换字幕分栏成功
  522. document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
  523. }, 500)
  524. document.querySelector('.vp-video__subtitle-text').style.display = 'block'
  525. if (!settings.subtitles) {
  526. $msg.info('开始加载字幕文件')
  527. document.querySelector('.vp-video__subtitle-text-first').innerText = '字幕正在加载中...'
  528. document.querySelector('.vp-tabs__header-item:nth-child(2)').click();
  529. }
  530. if (!settings.subtitleAutoEnable && confirm('是否设置自动开启字幕?')) {
  531. GM_setValue('subtitleAutoEnable', true)
  532. }
  533. }
  534. settings.subtitle_enabled = !settings.subtitle_enabled
  535. })
  536. let rewindBtn = createButton('←15s', e => {
  537. if (video.currentTime - 15 >= 0) {
  538. video.currentTime -= 15;
  539. } else {
  540. video.currentTime = 0;
  541. }
  542. })
  543. let forwardBtn = createButton('15s→', e => {
  544. if (video.currentTime + 15 < video.duration) {
  545. video.currentTime += 15;
  546. } else {
  547. video.currentTime = video.duration;
  548. }
  549. })
  550. toolBox.appendChild(reloadBtn)
  551. toolBox.appendChild(subtitleBtn)
  552. toolBox.appendChild(rewindBtn)
  553. toolBox.appendChild(forwardBtn)
  554. let speedBox = document.createElement('select')
  555. speedBox.value = settings.lastPlaybackRate
  556. let speeds = [3, 2.25, 2, 1.75, 1.5, 1.25, 1, 0.75] //向文山修改处
  557. speeds.forEach(speed => {
  558. const option = document.createElement('option');
  559. option.textContent = speed == 1.5 ? '正常' : (speed + 'X'); //向文山修改处 设置option的文本值
  560. option.value = speed;
  561. speedBox.appendChild(option);
  562. });
  563. speedBox.addEventListener('change', event => {
  564. const selectedSpeed = event.target.value;
  565. settings.globalVideo.playbackRate = selectedSpeed;
  566. GM_setValue('lastPlaybackRate', selectedSpeed)
  567. });
  568. toolBox.appendChild(speedBox)
  569. let resolutionBox = document.createElement('select')
  570. resolutionBox.id = 'resolution-box'
  571. resolutionBox.addEventListener('change', event => {
  572. const selectedResolution = event.target.value;
  573. if (selectedResolution == '1080') {
  574. alert('1080无法播放好像。。。')
  575. settings.resolution = '720'
  576. } else {
  577. settings.resolution = selectedResolution;
  578. }
  579. fetchVideoM3U8()
  580. })
  581. toolBox.appendChild(resolutionBox)
  582. video.parentElement.parentElement.parentElement.appendChild(toolBox)
  583. initDraftExport()
  584. // 向文山修改处
  585. //设置字幕位置和大小
  586. // 设置字幕的位置(设置为相对位置,能解决全屏但是不能解决字幕栏的出现因此设置,由于是相对位置可以删除left:30%;)(设置absolute不能设置字幕位置)
  587. //(设置fixed或者absolute,能解决存在字幕栏的问题)(移到外面去字幕没有那么长,不过上下位置不能调节,20240122能调节上下位置)(看清来刺眼:background: #fff; color: #030b1a; opacity: 0.4;)
  588. //display: block会导致没有字幕时显示ai字样, top: 94%在主屏合适,在副屏要设置为95%,height: fit-content;会导致ai字幕老是显示2行,还删除了left: 50%; max-width: 70%; opacity: 0.8;
  589. var parentElement2 = document.querySelector('.vp-video__subtitle-text');
  590. //if (parentElement2) {
  591. // parentElement2.style = 'position: absolute; font-size: 20px; padding-top:5px; padding-bottom:25px; height: fit-content; left: 50%; top: 94%; opacity: 0.8;';
  592. parentElement2.style = 'position: absolute; top: 94%; font-size: 20px; padding-top:5px; padding-bottom:25px;';
  593. //}
  594.  
  595.  
  596. /* 已经转移到css中
  597. // 隐藏控制栏(包含进度条和播放工具条)(只显示字幕清晰度 全屏三个按钮)
  598. var parentElement4 = document.querySelector('.vp-video__control-bar');
  599. parentElement4.style = 'position: absolute; color: brown; bottom:-15%; ';
  600. var parentElement1 = document.querySelector('.vp-video .vp-video__control-bar--button.is-text');
  601. parentElement1.style = 'color: blue';
  602. var parentElement0 = document.querySelector('.vp-video .vp-video__control-bar--button');
  603. parentElement0.style = 'color: blue';
  604. */
  605.  
  606. //(颜色设置见后面)设置当前时间的在屏幕中的上下位置(不能设置; display: block)(若设置left:-1.5%,会导致全屏的时间看不清)
  607. var parentElement5= document.querySelector(".vp-video__control-bar--play-time-current");
  608. //parentElement5.style = 'position: relative;color: yellowgreen; top: 97%; opacity: 1'; //显示在视频中
  609. parentElement5.style = 'position: absolute; right: 50%;top: 100.4%; opacity: 1'; //显示在屏幕下方
  610. //(颜色设置见后面)设置视频总时间的在屏幕中的上下位置(不能设置; display: block)
  611. var parentElement6= document.querySelector(".vp-video__control-bar--play-time-all");
  612. //parentElement6.style = 'position: relative; left:93%;color: yellowgreen; top: 94.5%; opacity: 1';//显示在视频中
  613. parentElement6.style = 'position: relative; left:93%;color: yellowgreen; top: 100.4%; opacity: 1';//显示在屏幕下方
  614.  
  615. // 设置进度条的颜色为没有颜色(注意style在语句中是style=background,属性中是vs下面就是background)(不能同时搜索多个class)()
  616. // var parentElement13 = document.querySelector('.vp-video__control-bar--play-time');
  617. //parentElement13.style = 'background: 0; color: yellowgreen; opacity: 0';//注释此句后可以看到蓝色进度条
  618. var parentElement14 = document.querySelector("#vjs_video_596 > div.vp-video__control-bar--play-time-all > div");
  619. parentElement14.style = 'background: 0; color: brown; font-size: 15px ; opacity: 1';
  620. var parentElement15 = document.querySelector("#vjs_video_596 > div.vp-video__control-bar--play-time-current > div")
  621. parentElement15.style = 'background: 0; color: brown; font-size: 15px; opacity: 1 ';
  622.  
  623. /* 已经写入css //设置视频标题的位置和文本大小 //<div class="vp-toolsbar" style="">
  624. var parentElement16 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div.vp-toolsbar")
  625. //parentElement16.style = 'position: absolute; top: -4.6%; left: 50%; transform: translate(-50%, -20%);justify-content: center; font-size: 14px;' // 视频标题显示在顶部 //被css代替
  626. parentElement16.style = 'position: relative; left: 70%; transform: translate(-50%, -20%);justify-content: center; font-size: 14px;' // 视频标题显示在底部 //不用设置:视频标题本来就在底部
  627. var parentElement17 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div.vp-toolsbar > section.vp-toolsbar__title")
  628. parentElement17.style = 'font-size: 14px;'
  629. */
  630.  
  631.  
  632. /* (无效)设置字幕位置
  633. var parentElement20 = document.querySelector("#vjs_video_596 > div.vp-video__subtitle-text")
  634. parentElement20.style = 'height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;' // 视频标题显示在顶部
  635. //#vjs_video_596 > div.vp-video__subtitle-text{height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;}
  636. var parentElement21 = document.querySelector("#vjs_video_596 > div.vp-video__subtitle-text.show")
  637. parentElement21.style = 'height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;' // 视频标题显示在顶部
  638. */
  639. //移除广告
  640. //案例var parentElement22 = document.querySelectorAll("[class*='current' i][class*='time' i],[class*='cur' i][class*='time' i],[class*='vjs'][class*='time'], *[class*='display'], *[class*='tooltip'], *[class*='playtime'], .hv_time span:first-child");
  641. //var parentElement22 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block")
  642. //var parentElement22 = document.querySelectorAll("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block")
  643. var parentElement22 = [
  644. //用于百度网盘,不能颠倒顺序,否则,会导致分享页面的视频的当前时间读取为0:0:0
  645. document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block"),
  646. //用于百度网盘,
  647. document.querySelector(".vjs-time-tooltip"),
  648. //用于bilibili的
  649. document.querySelector(".bpx-player-ctrl-time-current")
  650. ];
  651. parentElement22.style = ' display: none;'
  652.  
  653. /* .vp-header{display: none !important;}没有必要(因为顶部栏取消不了)
  654.  
  655. .vjs-progress-control{
  656. display: none !important;
  657. }
  658. .video-js .vjs-control-bar 取消视频控制栏的阴影
  659. vp-toolsbar__tools-block 屏蔽分享下载按钮
  660. li.vp-menu-item:nth-of-type(1) 屏蔽标题栏中的消息按钮 //不屏蔽
  661. .ai-course__feedback-container 课件中的反馈符号
  662. .vp-vip-pri 屏蔽视频页中的右下角特权介绍
  663. .vp-personal-aside.vp-aside 屏蔽百度网盘标题栏右侧
  664. */
  665.  
  666.  
  667.  
  668.  
  669. if (settings.subtitleAutoEnable) {
  670. setTimeout(() => {
  671. subtitleBtn.click();
  672. (function() {
  673. 'use strict';
  674.  
  675. // 创建一个 MutationObserver 实例-
  676. const observer = new MutationObserver(function(mutationsList) {
  677. for (let mutation of mutationsList) {
  678. // 检查是否有新添加的 ai-draft__wrap-list 子元素
  679. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  680. mutation.addedNodes.forEach(function(addedNode) {
  681. // 获取 ai-draft__wrap-list 元素
  682. var parentElement = document.querySelector('.ai-draft__wrap-list');
  683. // 设置字幕为可编辑
  684. parentElement.contentEditable = true;
  685. /*已经移到外面去了
  686. // 设置字幕的位置(设置为相对位置,能解决全屏但是不能解决字幕栏的出现因此设置,由于是相对位置可以删除left:30%;)(设置absolute不能设置字幕位置)
  687. //(设置fixed或者absolute,能解决存在字幕栏的问题)(移到外面去字幕没有那么长,不过上下位置不能调节,20240122能调节上下位置)(看清来刺眼:background: #fff; color: #030b1a; opacity: 0.4;)
  688. //parentElement2.style = 'position: relative; display: block; top: 85%; font-size: 15px; opacity: 0.4;'; //这是原来的,会导致字幕栏一行铺满视频
  689. var parentElement2 = document.querySelector('.vp-video__subtitle-text');
  690. parentElement2.style = 'position: absolute; display: block; top: 94%; font-size: 20px; padding-top:5px; padding-bottom:25px';
  691. */
  692. /* 已经移动到脚本开始处
  693. // 设置进度条的颜色为没有颜色(注意style在语句中是style=background,属性中是vs下面就是background)(不能同时搜索多个class)(青蓝色aqua aquamarine 紫色blueviolet 棕红色brown 黄色burlywood 深绿cadetblue 墨水蓝cornflowerblue 灰色gray 淡绿yellowgreen)
  694. var parentElement3 = document.querySelector('.vjs-control-bar');
  695. parentElement3.style = 'background: 0;opacity: 0.5; position: relative; bottom:1%;';//注释此句后可以看到蓝色进度条
  696.  
  697.  
  698. // 隐藏控制栏(包含进度条和播放工具条)
  699. var parentElement4 = document.querySelector('.vp-video__control-bar');
  700. parentElement4.style = 'position: absolute; color: brown; bottom:-15%; ';
  701. var parentElement1 = document.querySelector('.vp-video .vp-video__control-bar--button.is-text');
  702. parentElement1.style = 'color: blue';
  703. var parentElement0 = document.querySelector('.vp-video .vp-video__control-bar--button');
  704. parentElement0.style = 'color: blue';
  705. */
  706.  
  707.  
  708. /*
  709.  
  710. //(不用管进度条了,调节不了算了) 显示进度条(设为relative为79%)(为了可以避免进度点动态显示设为absolute为79%,但是设置不了位置)
  711. var parentElemen7 = document.querySelector("#vjs_video_596 > div.vjs-control-bar > div.vjs-control-bar > div.vjs-progress-control.vjs-control.vp-video-progress-control");
  712. parentElemen7.style = 'position: relative;top:90%; opacity: 1';
  713. // 显示当前进度条-显示蓝色进度
  714. var parentElemen8 = document.querySelector(".vjs-play-progress.vjs-slider-bar");
  715. parentElemen8.style = 'position: relative;top:90%; opacity: 1';
  716. // 显示当前进度条-解锁进度条的点击功能(用了relative,进度条不能拖动)(设为relative为79%)
  717. var parentElemen9 = document.querySelector(".vjs-progress-holder.vjs-slider.vjs-slider-horizontal");
  718. parentElemen9.style = 'position: relative; top:90%; opacity: 1';
  719. // 显示当前进度条-暂停时进度原点自动调到对应位置
  720. //var parentElemen10 = document.querySelector(".vp-video-progress-control");
  721. //var parentElemen10 = document.querySelector(".video-js.vjs-control-bar.vp-video-progress-control.vjs-play-progress:before");
  722. // parentElemen10.style = 'position: relative; top:79%; opacity:1';
  723. // 无效
  724.  
  725. //var parentElemen11 = document.querySelector(".vp-video-progress-control.vjs-control.vjs-progress-control");
  726. //parentElemen11.style = 'position: relative; top:90%; opacity: 1';
  727.  
  728. //var parentElemen16 = document.querySelector(".vp-video-progress-control.vjs-control.vjs-progress-control > .vjs-slider-horizontal.vjs-slider.vjs-progress-holder > .vjs-slider-bar.vjs-play-progress");
  729. //parentElemen16.style = 'position: relative; top:90%; opacity: 1';
  730. //var parentElemen17 = document.querySelector(".vp-video-progress-control.vjs-control.vjs-progress-control > .vjs-slider-horizontal.vjs-slider.vjs-progress-holder");
  731. //parentElemen17.style = 'position: relative; top:90%; opacity: 1';
  732. //var parentElemen12 = document.querySelector(".vjs-play-progress .vjs-slider-bar");
  733. //parentElemen12.style = 'position: relative; top:90%; opacity: 1';
  734. */
  735.  
  736.  
  737.  
  738. // 获取音量控制面板元素
  739. var volumePane2 = document.querySelector('.vjs-volume-panel.vjs-control.vjs-volume-panel-vertical');
  740.  
  741. // 如果找到了音量控制面板元素,则隐藏它
  742. if (volumePane2) {
  743. volumePane2.style = 'opacity: 1';
  744. volumePane2.style.display = 'none';
  745. }
  746.  
  747.  
  748.  
  749. //获取视频播放器控制栏元素(查询多类,不用空格分隔类)
  750. //var volumePanel = document.querySelectorAll('.vjs-control-bar.video-js');
  751.  
  752. //无效-如果找到了音量控制面板元素,则隐藏它
  753. // if (volumePanel) {
  754. // volumePanel.style = 'opacity: 0';
  755. //volumePanel.style.display = 'none';
  756.  
  757. // }
  758.  
  759.  
  760. });
  761. }
  762. }
  763. });
  764.  
  765. // 在整个文档上启动 MutationObserver
  766. observer.observe(document.documentElement, { childList: true, subtree: true });
  767. })();
  768.  
  769. }, 1500)
  770. }
  771.  
  772. //FIX: 修改部分css修复宽度小时无法显示全的问题
  773. document.getElementById('app').style.width = '100%'
  774. document.querySelector('.vp-toolsbar').remove()
  775. document.querySelector('.vp-header').style.minWidth = '0'
  776. document.querySelector('.vp-personal-video-play').style.minWidth = '0'
  777. document.querySelector('.vp-personal-home-layout__video').style.minWidth = '30vw'
  778. document.querySelector('.vp-personal-home-layout__video').style.paddingTop = '10px'
  779. document.querySelector('.vp-personal-home-layout__video').style.height = '80vh'
  780. document.querySelector('.vp-personal-home-layout').style.padding = '0 20px'
  781. document.querySelector('.vp-personal-home-layout .vp-aside').style.paddingTop = '0'
  782. document.querySelector('.vp-tabs').style.minWidth = '10vw'
  783. if (settings.lastTabWidth) {
  784. document.querySelector('.vp-personal-home-layout .vp-aside').style.width = settings.lastTabWidth
  785. }
  786. function addSplitScreenAdjustment(element1Selector, element2Selector) {
  787. const element1 = document.querySelector(element1Selector);
  788. const element2 = document.querySelector(element2Selector);
  789. let scriptChanging = false;
  790. const handle = document.createElement('div');
  791. handle.style.width = '15px';
  792. handle.style.cursor = 'ew-resize';
  793. handle.style.backgroundColor = '#ccc';
  794. handle.style.margin = '0 5px';
  795. let startX, startWidth1, startWidth2;
  796. const handleMouseDown = (event) => {
  797. startX = event.clientX;
  798. startWidth1 = element1.offsetWidth;
  799. startWidth2 = element2.offsetWidth;
  800. scriptChanging = true
  801. document.addEventListener('mousemove', handleMouseMove);
  802. document.addEventListener('mouseup', handleMouseUp);
  803. };
  804. const handleMouseMove = throttle((event) => {
  805. const dx = event.clientX - startX;
  806. const newWidth1 = startWidth1 + dx;
  807. const newWidth2 = startWidth2 - dx;
  808. element1.style.width = newWidth1 + 'px';
  809. element2.style.width = newWidth2 + 'px';
  810. }, 100);
  811. const handleMouseUp = () => {
  812. document.removeEventListener('mousemove', handleMouseMove);
  813. document.removeEventListener('mouseup', handleMouseUp);
  814. scriptChanging = false
  815. GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
  816. };
  817. handle.addEventListener('mousedown', handleMouseDown);
  818. const handleTouchStart = (event) => {
  819. startX = event.touches[0].clientX;
  820. startWidth1 = element1.offsetWidth;
  821. startWidth2 = element2.offsetWidth;
  822. scriptChanging = true
  823. document.addEventListener('touchmove', handleTouchMove);
  824. document.addEventListener('touchend', handleTouchEnd);
  825. };
  826. const handleTouchMove = throttle((event) => {
  827. const dx = event.touches[0].clientX - startX;
  828. const newWidth1 = startWidth1 + dx;
  829. const newWidth2 = startWidth2 - dx;
  830. element1.style.width = newWidth1 + 'px';
  831. element2.style.width = newWidth2 + 'px';
  832. }, 100);
  833. const handleTouchEnd = () => {
  834. document.removeEventListener('touchmove', handleTouchMove);
  835. document.removeEventListener('touchend', handleTouchEnd);
  836. scriptChanging = false
  837. GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
  838. };
  839. handle.addEventListener('touchstart', handleTouchStart);
  840. element2.parentNode.insertBefore(handle, element2);
  841. //防止百度网盘自动调整他的宽度
  842. var observer = new MutationObserver(function(mutations) {
  843. mutations.forEach(function(mutation) {
  844. const oldStyle = mutation.oldValue;
  845. if (!scriptChanging) {
  846. observer.disconnect()
  847. setTimeout(() => {
  848. element2.style = oldStyle;
  849. observer.observe(element2, {attributes: true, attributeOldValue: true})
  850. }, 50)
  851. }
  852. });
  853. });
  854. observer.observe(element2, { attributes: true, attributeOldValue: true });
  855. }
  856. addSplitScreenAdjustment('main', '.vp-personal-home-layout .vp-aside')
  857.  
  858. let rateOptions = document.querySelector('.vp-video__control-bar--playback-rates').children
  859. for (let i = 0; i < rateOptions.length; i++) {
  860. let option = rateOptions[i]
  861. if (option.classList[1] != 'is-svip-guide') {
  862. option.onclick = function(e) {
  863. e.stopPropagation()
  864. GM_setValue('lastPlaybackRate', Number.parseFloat(option.innerText.replace('X', '')))
  865. video.playbackRate = settings.lastPlaybackRate
  866. }
  867. }
  868. }
  869.  
  870. //双击暂停
  871. let container = video.parentElement;
  872. let pauseThreshold = 500; // milliseconds
  873. let lastClickTime = 0;
  874. let pauseTimer;
  875. container.addEventListener("touchstart", function(event) {
  876. let currentTime = new Date().getTime();
  877. if (currentTime - lastClickTime < pauseThreshold) {
  878. clearTimeout(pauseTimer);
  879. if (video.paused) {
  880. video.play();
  881. } else {
  882. video.pause();
  883. }
  884. } else {
  885. lastClickTime = currentTime;
  886. pauseTimer = setTimeout(function() {
  887. lastClickTime = 0;
  888. }, pauseThreshold);
  889. }
  890. });
  891.  
  892. //FIX: 替换自带全屏修复Focus浏览器全屏使用iOS默认播放器
  893. let vpVideo = document.querySelector('.vp-video')
  894. let controllBar = document.querySelector('.vp-video__control-bar--setup')
  895. controllBar.children[4].remove()
  896. controllBar.insertAdjacentHTML('beforeend', `<div class="vp-video__control-bar--button-group">
  897. <div class="vp-video__control-bar--button is-icon">
  898. <i class="u-icon-screen"></i></div></div>`)
  899. let fullScreenBtn = controllBar.children[4]
  900. let fullScreenIcon = fullScreenBtn.children[0].children[0]
  901. fullScreenBtn.onclick = e => {
  902. if (fullScreenIcon.className == 'u-icon-screen') {
  903. //点击全屏
  904. vpVideo.style.zIndex = '99999'
  905. vpVideo.style.position = 'fixed';
  906. vpVideo.style.top = '0';
  907. vpVideo.style.left = '0';
  908. fullScreenIcon.className = 'u-icon-exit-screen'
  909. } else {
  910. vpVideo.style.position = 'unset';
  911. fullScreenIcon.className = 'u-icon-screen'
  912. }
  913. }
  914.  
  915. //准备完毕,开始检查视频状态
  916. startFailChecker()
  917. }
  918. }
  919.  
  920. function createButton(textContent, callback) {
  921. let btn = document.createElement('div')
  922. btn.style = 'padding: 10px 10px; text-align: center; background: rgb(6, 167, 255); color: white;font-size: 14px;cursor:pointer'
  923. btn.textContent = textContent
  924. if (callback) {
  925. btn.onclick = e => {
  926. callback(e)
  927. }
  928. }
  929. return btn;
  930. }
  931.  
  932. function formatPlayTime(timestamp) {
  933. let current = new Date().getTime()
  934. let gap = Math.floor((current - timestamp) / 1000)
  935. if (gap < 60) {
  936. return `${gap}秒前`
  937. } else if (gap < 60 * 60) {
  938. return `${Math.floor(gap/60)}分钟前`
  939. } else if (gap < 60 * 60 * 24) {
  940. return `${Math.floor(gap/60/60)}小时前`
  941. } else {
  942. return new Date(timestamp).toLocaleString()
  943. }
  944. }
  945. function initPlayHistory() {
  946. let style = document.createElement('style')
  947. 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;}';
  948. document.head.append(style)
  949. let historyWrapper = document.createElement('div')
  950. historyWrapper.className = 'history-wrapper'
  951. historyWrapper.innerHTML = `<span style="float:left;margin-top: 5px;font-weight: bold;">播放历史(保留20条)</span>
  952. <span class="close" onclick="closeModal()">&times;</span>
  953. <div style="clear:both;"></div>
  954. <div class="history-list"></div>`
  955. historyWrapper.querySelector('.close').onclick = function(e) {
  956. historyWrapper.style.display = 'none'
  957. }
  958. document.body.append(historyWrapper)
  959. var existingHistoryIndex = settings.histories.findIndex(function(item) {
  960. return item.path === settings.path;
  961. });
  962. if (existingHistoryIndex != -1 && settings.globalVideo) {
  963. settings.globalVideo.currentTime = settings.histories[existingHistoryIndex].current
  964. }
  965. loadHistories()
  966. }
  967. function loadHistories() {
  968. let historyList = document.querySelector('.history-list')
  969. historyList.innerHTML = ''
  970. for (let i = settings.histories.length - 1; i >= 0; i--) {
  971. let history = settings.histories[i]
  972. let historyElem = document.createElement('div')
  973. historyElem.className = 'history'
  974. historyList.append(historyElem)
  975. historyElem.innerHTML = `<div class="history-time">${formatPlayTime(history.timestamp)}</div>
  976. <div class="history-title">${history.title}</div>
  977. <div class="history-progress-bar">
  978. <div class="history-progress" style="width:${100*history.current/history.duration}%"></div>
  979. </div>
  980. <span style="color:rgb(100,100,100);font-size:12px;">观看至${formatTime(history.current)}</span>`
  981. historyElem.setAttribute('path', history.path)
  982. }
  983. historyList.onclick = function(e) {
  984. const elem = event.target.closest('.history');
  985. if (elem) {
  986. const path = elem.getAttribute('path')
  987. if (confirm('跳转视频' + path + '?')) {
  988. location.href = 'https://pan.baidu.com/pfile/video?path=' + encodeURIComponent(path)
  989. }
  990. }
  991. }
  992. historyList.innerHTML += `<div class="history-end">—————— 已经到底了哦 ——————</div>`
  993. }
  994. function updateHistory(history) {
  995. const histories = settings.histories;
  996. var existingHistoryIndex = histories.findIndex(function(item) {
  997. return item.path === history.path;
  998. });
  999. if (existingHistoryIndex !== -1) {
  1000. histories.splice(existingHistoryIndex, 1)[0];
  1001. histories.push(history);
  1002. } else {
  1003. histories.push(history);
  1004. if (histories.length > 20) {
  1005. histories.splice(0, 1)
  1006. }
  1007. }
  1008. GM_setValue('histories', histories);
  1009. }
  1010.  
  1011. function hookRequest() {
  1012. var originOpen = XMLHttpRequest.prototype.open;
  1013. XMLHttpRequest.prototype.open = function (method, url) {
  1014. if (url.indexOf('netdisk-subtitle') != -1 && settings.solve_subtitle) { //获取字幕信息
  1015. this.addEventListener('readystatechange', function() {
  1016. if (this.readyState == 4) {
  1017. var blobData = this.response;
  1018. var reader = new FileReader();
  1019. reader.onloadend = function() {
  1020. var textContent = reader.result;
  1021. let arr = textContent.split('\n')
  1022. setTimeout(() => {
  1023. let wrapper = document.querySelector('.ai-draft__wrap-list')
  1024. try {
  1025. document.querySelector('.ai-draft__wrap-content').style = 'padding-right:12px!important;'
  1026. document.querySelector('.ai-draft__wrap-content').contentEditable = true //向文山修改处-在1.35及之前需要注销此句,否则会导致视频倍数插件的c键失效,现在复制文本,可以复制到此处,也可以复制到计时器中
  1027. document.querySelector('.ai-draft__filter').remove()
  1028. document.querySelector('.ai-draft__svip-guide').remove()
  1029. } catch(err) {
  1030. }
  1031. let subtitles = []
  1032. let paragraph
  1033. console.log('开始解析字幕')
  1034. for (let i = 1; i < arr.length / 4; i++) {
  1035. if (arr[i * 4] == '')
  1036. break;
  1037. let lines = [arr[i * 4], arr[i * 4 + 1], arr[i * 4 + 2]]
  1038. let timeParts = lines[1].split(' --> ');
  1039. let startTime = srtTimeToSeconds(timeParts[0].trim());
  1040. let endTime = srtTimeToSeconds(timeParts[1].trim());
  1041. if (!settings.isSvip && i >= 15 * 3 + 1) {
  1042. if (i % 15 == 1) {
  1043. paragraph = document.createElement('p')
  1044. paragraph.className = 'ai-draft__p-paragraph'
  1045. paragraph.setAttribute('data-starttime', startTime * 1000)
  1046. } else if (i % 15 == 0) {
  1047. wrapper.appendChild(paragraph)
  1048. }
  1049. let span = document.createElement('span')
  1050. span.className = 'ai-draft__p-sentence'
  1051. span.setAttribute('data-index', i - 1)
  1052. span.innerText = lines[2]
  1053. span.onclick = e => {
  1054. settings.globalVideo.currentTime = startTime
  1055. }
  1056. paragraph.appendChild(span)
  1057. }
  1058. subtitles.push({
  1059. index: i - 1,
  1060. startTime: startTime,
  1061. endTime: endTime,
  1062. text: lines[2]
  1063. })
  1064. }
  1065. settings.subtitles = subtitles
  1066. }, 1000)
  1067. };
  1068. reader.readAsText(blobData);
  1069. }
  1070. });
  1071. originOpen.apply(this, arguments);
  1072. } else if (url.indexOf('/api/loginStatus') != -1) { //伪造svip信息
  1073. this.addEventListener('readystatechange', function() {
  1074. if (this.readyState == 4) {
  1075. let res = JSON.parse(this.responseText)
  1076. settings.isSvip = res.login_info.vip_type == '21'
  1077. res.login_info.vip_type = '21'
  1078. res.login_info.vip_identity = '21'
  1079. res.login_info.vip_level = 8
  1080. res.login_info.vip_point = 99999
  1081. res.login_info.username = 'sout(\'GwenCrack\')'
  1082. settings.bdstoken = res.login_info.bdstoken
  1083. Object.defineProperty(this, "responseText", {
  1084. writable: true,
  1085. });
  1086. this.responseText = JSON.stringify(res)
  1087. }
  1088. })
  1089. originOpen.apply(this, arguments);
  1090. } else if (url.indexOf('/user/info') != -1) {
  1091. this.addEventListener('readystatechange', function() {
  1092. if (this.readyState == 4) {
  1093. let res = JSON.parse(this.responseText)
  1094. res.user_info.is_vip = 1
  1095. res.user_info.is_svip = 1
  1096. res.user_info.is_plus_buy = 1
  1097. Object.defineProperty(this, "responseText", {
  1098. writable: true,
  1099. });
  1100. this.responseText = JSON.stringify(res)
  1101. }
  1102. })
  1103. originOpen.apply(this, arguments);
  1104. } else if (url.indexOf('/membership/user') != -1) {
  1105. this.addEventListener('readystatechange', function() {
  1106. if (this.readyState == 4) {
  1107. let res = JSON.parse(this.responseText)
  1108. res.reminder = {
  1109. "svip": {
  1110. "leftseconds": 9999999999,
  1111. "nextState": "normal"
  1112. }
  1113. }
  1114. res.level_info = {
  1115. "current_value": 12090,
  1116. "current_level": 10,
  1117. "history_value": 11830,
  1118. "history_level": 10,
  1119. "v10_id": "666666",
  1120. "last_manual_collection_time": 0
  1121. }
  1122. res.product_infos = [{
  1123. "product_id": "",
  1124. "start_time": 1685635199,
  1125. "end_time": 1888227199,
  1126. "buy_time": 0,
  1127. "cluster": "vip",
  1128. "detail_cluster": "svip",
  1129. "auto_upgrade_to_svip": 0,
  1130. "product_name": "svip2_nd",
  1131. "status": 0,
  1132. "function_num": 0,
  1133. "buy_description": "",
  1134. "product_description": "",
  1135. "cur_svip_type": "month"
  1136. }]
  1137. res.current_product = {
  1138. "cluster": "vip",
  1139. "detail_cluster": "svip",
  1140. "product_type": "vip2_1m_auto",
  1141. "product_id": "12187135090581539740"
  1142. }
  1143. res.current_product_v2 = {
  1144. "cluster": "vip",
  1145. "detail_cluster": "svip",
  1146. "product_type": "vip2_1m_auto",
  1147. "product_id": "12187135090581539740"
  1148. }
  1149. Object.defineProperty(this, "responseText", {
  1150. writable: true,
  1151. });
  1152. this.responseText = JSON.stringify(res)
  1153. }
  1154. })
  1155. originOpen.apply(this, arguments);
  1156. } else if (url.indexOf('/api/streaming') != -1 && url.indexOf('M3U8_SUBTITLE_SRT') == -1) { //获取视频m3u8接口
  1157. let modifiedUrl = url.replace(/vip=2/, 'vip=0')
  1158. // .replace(/M3U8_.*?&/, 'M3U8_AUTO_1080&')
  1159. if (settings.adToken) {
  1160. modifiedUrl += ('&adToken=' + encodeURIComponent(settings.adToken))
  1161. settings.adToken = null
  1162. }
  1163. originOpen.call(this, method, modifiedUrl, arguments[2]);
  1164. this.addEventListener('readystatechange', function() {
  1165. if (this.readyState == 4) {
  1166. if (this.responseText[0] == '{') {
  1167. let res = JSON.parse(this.responseText)
  1168. settings.adToken = res.adToken
  1169. res.ltime = 0.001
  1170. res.adTime = 0.001
  1171. Object.defineProperty(this, "responseText", {
  1172. writable: true,
  1173. });
  1174. this.responseText = JSON.stringify(res)
  1175. if (settings.isSvip) {
  1176. settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
  1177. let xhr = new XMLHttpRequest()
  1178. 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` +
  1179. `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${settings.path}&jsToken=${unsafeWindow.jsToken}`
  1180. xhr.open("GET", url, false)
  1181. xhr.send()
  1182. this.responseText = xhr.responseText
  1183. } else if (this.callback) {
  1184. this.callback()
  1185. }
  1186. } else {
  1187. let m3u8Content = this.responseText
  1188. let blob = new Blob([this.responseText], { type: 'application/vnd.apple.mpegurl' });
  1189. let url = URL.createObjectURL(blob);
  1190. bindHls(url)
  1191. }
  1192. }
  1193. })
  1194. } else if (url.indexOf('/api/filemetas') != -1) {
  1195. this.addEventListener('readystatechange', function() {
  1196. if (this.readyState == 4) {
  1197. let res = JSON.parse(this.responseText)
  1198. if (res.info.length != 1)
  1199. return
  1200. let resolution = res.info[0].resolution
  1201. console.log('分辨率'+ resolution)
  1202. let resolutionOptions = []
  1203. let match = false
  1204. switch(resolution) {
  1205. case 'width:1920,height:1080':
  1206. match = true
  1207. resolutionOptions.push('1080')
  1208. case 'width:1280,height:720':
  1209. match = true
  1210. resolutionOptions.push('720')
  1211. case 'width:720,height:480':
  1212. match = true
  1213. resolutionOptions.push('480')
  1214. default:
  1215. resolutionOptions.push('360');
  1216. }
  1217. if (!match) {
  1218. resolutionOptions = ['720', '480', '360']
  1219. }
  1220. console.log(resolutionOptions)
  1221. let waitTimer = setInterval(() => {
  1222. let box = document.getElementById('resolution-box')
  1223. if (box) {
  1224. clearInterval(waitTimer)
  1225. box.innerHTML = ''
  1226. resolutionOptions.forEach(resolution => {
  1227. const option = document.createElement('option')
  1228. option.textContent = resolution + 'P'
  1229. option.value = resolution
  1230. box.appendChild(option)
  1231. })
  1232. }
  1233. }, 400)
  1234. }
  1235. })
  1236. originOpen.apply(this, arguments);
  1237. } else if (url.indexOf('/msg/streaming') != -1 || url.indexOf('/share/streaming') != -1) {
  1238. this.addEventListener('readystatechange', function() {
  1239. if (this.readyState == 4) {
  1240. if (this.responseText[0] != '{')
  1241. return
  1242. let res = JSON.parse(this.responseText)
  1243. res.ltime = 0.000001
  1244. res.adTime = 0.000001
  1245. Object.defineProperty(this, 'responseText', {
  1246. writable: true,
  1247. })
  1248. this.responseText = JSON.stringify(res)
  1249. }
  1250. })
  1251. originOpen.apply(this, arguments);
  1252. } else {
  1253. originOpen.apply(this, arguments);
  1254. }
  1255. }
  1256. }
  1257.  
  1258. function bindHls(url) {
  1259. if (!Hls.isSupported()) {
  1260. $msg.error('浏览器不支持播放')
  1261. return
  1262. }
  1263. if (settings.hls) {
  1264. try {
  1265. settings.hls.destroy()
  1266. settings.hls = null
  1267. } catch(err) {
  1268. console.error(err)
  1269. }
  1270. }
  1271. settings.hls = new Hls({
  1272. autoStartLoad: true,
  1273. autoplay: true
  1274. })
  1275. let hls = settings.hls
  1276. let video = settings.globalVideo
  1277. let vpError = document.querySelector('.vp-error')
  1278. if (vpError) {
  1279. vpError.remove()
  1280. }
  1281. hls.on(Hls.Events.MEDIA_ATTACHED, function() {
  1282. hls.loadSource(url);
  1283. });
  1284. hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
  1285. console.log('上次加载到' + settings.lastCurrentTime)
  1286. video.currentTime = settings.lastCurrentTime
  1287. video.play();
  1288. video.playbackRate = settings.lastPlaybackRate
  1289. let checkDurationTimer = setInterval(() => {
  1290. if (video.readyState > 0) {
  1291. document.querySelector('.vp-video__control-bar--play-time-all>div').innerText = formatTime(video.duration)
  1292. clearInterval(checkDurationTimer)
  1293. }
  1294. }, 100)
  1295. });
  1296. hls.attachMedia(video);
  1297. }
  1298. function srtTimeToSeconds(timeString) {
  1299. var timeParts = timeString.split(':');
  1300. var hours = parseInt(timeParts[0]);
  1301. var minutes = parseInt(timeParts[1]);
  1302. var secondsAndMilliseconds = timeParts[2].split('.');
  1303. var seconds = parseInt(secondsAndMilliseconds[0]);
  1304. var milliseconds = parseInt(secondsAndMilliseconds[1]);
  1305. var totalSeconds = (hours * 3600) + (minutes * 60) + seconds + (milliseconds / 1000);
  1306. return totalSeconds;
  1307. }
  1308.  
  1309. function formatTime(totalSeconds, requireMil = false) {
  1310. var hours = Math.floor(totalSeconds / 3600);
  1311. var minutes = Math.floor((totalSeconds % 3600) / 60);
  1312. var seconds = Math.floor(totalSeconds % 60);
  1313. var formattedTime = hours.toString().padStart(2, '0') + ':' +
  1314. minutes.toString().padStart(2, '0') + ':' +
  1315. seconds.toString().padStart(2, '0');
  1316. if (requireMil) {
  1317. formattedTime += ',' + (seconds % 1).toFixed(3).substring(2)
  1318. }
  1319. return formattedTime;
  1320. }
  1321. function copyToClipboard(txt){
  1322. if (navigator.clipboard?.writeText)
  1323. navigator.clipboard.writeText(txt)
  1324. else {
  1325. input = document.createElement('textarea')
  1326. input.setAttribute('readonly', 'readonly')
  1327. input.value = txt
  1328. document.body.appendChild(input)
  1329. input.select()
  1330. if (document.execCommand('copy'))
  1331. document.execCommand('copy')
  1332. document.body.removeChild(input)
  1333. }
  1334. }
  1335. function initDraftExport() {
  1336. function getDefaultFilename(ext) {
  1337. var videoNameElement = document.querySelector('div.vp-video-page-card span.is-playing.vp-video-page-card__video-name');
  1338. if (videoNameElement) {
  1339. var originalFilename = videoNameElement.innerText.trim();
  1340. var newFilename = originalFilename.replace(/\.[^/.]+$/, '') + ext; // 去掉原始文件名的后缀,并添加新的后缀名
  1341. return newFilename;
  1342. }
  1343. return 'subtitle' + ext;
  1344. }
  1345. let toolBox = document.getElementById('toolBox')
  1346. // 创建复制字幕按钮
  1347. function createDraftButton(id, textContent, left, callback) {
  1348. const btn = document.createElement('button');
  1349. btn.id = id;
  1350. btn.innerText = textContent;
  1351. // btn.style = `position:fixed;left:${left};bottom:3px;z-index:9999;padding:10px;background:#fff;border:1px solid #ccc;cursor:pointer;`
  1352. btn.style = 'padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'
  1353. // 复制字幕按钮点击事件处理函数
  1354. btn.addEventListener('click', function() {
  1355. if (!settings.subtitles) {
  1356. $msg.info('视频文稿未加载,开始加载...')
  1357. document.querySelector('.vp-tabs__header-item:nth-child(4)').click(); //向文山修改处 4表示字幕分栏在右侧导航栏中的位置,ps:要全部搜索修改.vp-tabs__header-item:nth-child(2)和(1),共修改5次
  1358. setTimeout(() => {
  1359. document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
  1360. }, 500)
  1361. return
  1362. }
  1363. callback()
  1364. });
  1365. toolBox.append(btn)
  1366. }
  1367. createDraftButton('copySubtitleBtn', '复制字幕', '40px', function() {
  1368. const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
  1369. const subtitleText = [];
  1370. for (let i = 0; i < subtitleElements.length; i++) {
  1371. subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
  1372. }
  1373. copyToClipboard(subtitleText.join('\n\n')); // 将字幕数组以空行连接起来并返回
  1374. $msg.success('字幕已复制')
  1375. })
  1376. createDraftButton('exportToDocBtn', '导出文稿doc', '120px', function() {
  1377. $msg.info('正在导出文稿...')
  1378. const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
  1379. const subtitleText = [];
  1380. for (let i = 0; i < subtitleElements.length; i++) {
  1381. subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
  1382. }
  1383. const subtitle = subtitleText.join('\n\n'); // 获取字幕内容
  1384. const filename = getDefaultFilename('.doc');
  1385. var blob = new Blob([subtitle], {type: 'text/plain;charset=utf-8'});
  1386. var downloadLink = document.createElement("a");
  1387. downloadLink.href = URL.createObjectURL(blob);
  1388. downloadLink.download = filename;
  1389. document.body.appendChild(downloadLink);
  1390. downloadLink.click();
  1391. document.body.removeChild(downloadLink);
  1392. URL.revokeObjectURL(downloadLink.href);
  1393. //saveAs(blob, filename); // 使用FileSaver.js保存文件
  1394. $msg.success('导出成功')
  1395. })
  1396. createDraftButton('exportToSrtBtn', '导出字幕srt', '220px', function() {
  1397. $msg.info('正在导出文稿...')
  1398. const blobArray = [];
  1399. settings.subtitles.forEach(function(subtitle) {
  1400. const srtText =
  1401. (subtitle.index + 1) + "\n" +
  1402. formatTime(subtitle.startTime, true) + " --> " + formatTime(subtitle.endTime, true) + "\n" +
  1403. subtitle.text + "\n\n";
  1404. console.log(srtText)
  1405. const srtBlob = new Blob([srtText], { type: "text/plain;charset=utf-8" });
  1406. blobArray.push(srtBlob);
  1407. });
  1408. var combinedBlob = new Blob(blobArray, { type: "text/plain;charset=utf-8" });
  1409. var downloadLink = document.createElement("a");
  1410. downloadLink.href = URL.createObjectURL(combinedBlob);
  1411. downloadLink.download = getDefaultFilename('.srt');
  1412. document.body.appendChild(downloadLink);
  1413. downloadLink.click();
  1414. document.body.removeChild(downloadLink);
  1415. URL.revokeObjectURL(downloadLink.href);
  1416. //saveAs(combinedBlob, getDefaultFilename('.srt'));
  1417. $msg.success('导出成功')
  1418. })
  1419. }
  1420.  
  1421. hookRequest()
  1422. init()
  1423. let localsTimer = setInterval(() => {
  1424. if (!unsafeWindow.locals) return
  1425. clearInterval(localsTimer)
  1426. console.log('设置window.locas', unsafeWindow.locals)
  1427. if (unsafeWindow.locals.userInfo) {
  1428. unsafeWindow.locals.userInfo.vip_level = 8
  1429. unsafeWindow.locals.userInfo.vip_identity = 21
  1430. unsafeWindow.locals.userInfo.username = "GwenCrackヾ(-_-;)"
  1431. } else if(unsafeWindow.locals.mset) {
  1432. unsafeWindow.locals.mset({
  1433. 'is_vip': 1,
  1434. 'is_svip': 1,
  1435. 'vip_level': 8,
  1436. 'show_vip_ad': 0
  1437. })
  1438. } else {
  1439. unsafeWindow.locals.vip_level = 8
  1440. unsafeWindow.locals.is_vip = 1
  1441. unsafeWindow.locals.is_svip = 1
  1442. unsafeWindow.locals.is_evip = 0
  1443. unsafeWindow.locals.show_vip_ad = 0
  1444. }
  1445. }, 100)
  1446. let lastUrl = location.href
  1447. setInterval(() => {
  1448. if (lastUrl != location.href) {
  1449. lastUrl = location.href
  1450. console.log('%cURL变化为' + location.href, 'color:purple;')
  1451. settings.path = new URLSearchParams(new URL(lastUrl).search).get('path');
  1452. setTimeout(() => {
  1453. $msg.info('重新加载字幕')
  1454. settings.subtitles = null
  1455. document.querySelector('.vp-tabs__header-item:nth-child(2)').click();
  1456. setTimeout(() => {
  1457. document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
  1458. }, 500)
  1459. }, 2500)
  1460.  
  1461. }
  1462. }, 500)
  1463.  
  1464. })()