// ==UserScript==
// @name 🔥【就是爽】【改进版】某度网盘 破解SVIP&&倍速播放&&文稿字幕&&样式&&解锁&&修复自动开启字幕,删广告样式等bug(360+chrome已测)....看网课专用!
// @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.6
// @description 直接上效果图,见下面........修复自动开启字幕和导出doc等bug,取消视频控制栏的阴影等等 (20240825修正到1.3.7→1.6,修正错误:开启脚本后无法加载字幕)
// @license none
// @author Gwen
// ==/UserScript==
(function() {
//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}')
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', true), //是否自动开启字幕 向文山修改处20240325 设为true和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', []),
}
if (location.href.indexOf('https://pan.baidu.com/disk/main') != -1) {
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()
let localsTimer = setInterval(() => {
if (!unsafeWindow.locals) return
clearInterval(localsTimer)
console.log('设置window.locas', unsafeWindow.locals)
let originalSet = unsafeWindow.locals.set
unsafeWindow.locals.set = function(n, t) {
console.log('%c[hook]' + n + ': ' + t, 'color:blue;')
if (['is_vip', 'is_svip'].indexOf(n) != -1) {
t = 1
} else if (n == 'vip_level') {
t = 10
} else if (n == 'v10_id') {
t = '666666'
}
console.log(arguments)
originalSet.apply(this, [n, t])
}
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': 10,
'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
}
}, 10)
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 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 = '20px' // 向文山修改处 将24改回
// 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;opacity: 0.8;pointer-events:none' //这是1.37版 //向文山备注处
setTimeout(() => { // 向文山修改处20240325 可实现凡是之前上一次设置过开启字幕,下一次加载视频按钮会显示"关闭字幕",就会自动点击字幕分栏,设置了setTimeout才会反复检查是否切换字幕分栏成功
document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
}, 500)
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(2)').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.25, 2, 1.75, 1.5, 1.25, 1, 0.75] //向文山修改处
speeds.forEach(speed => {
const option = document.createElement('option');
option.textContent = speed == 1.5 ? '正常' : (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)
video.parentElement.parentElement.parentElement.appendChild(toolBox)
initDraftExport()
// 向文山修改处
//设置字幕位置和大小
// 设置字幕的位置(设置为相对位置,能解决全屏但是不能解决字幕栏的出现因此设置,由于是相对位置可以删除left:30%;)(设置absolute不能设置字幕位置)
//(设置fixed或者absolute,能解决存在字幕栏的问题)(移到外面去字幕没有那么长,不过上下位置不能调节,20240122能调节上下位置)(看清来刺眼:background: #fff; color: #030b1a; opacity: 0.4;)
//display: block会导致没有字幕时显示ai字样, top: 94%在主屏合适,在副屏要设置为95%,height: fit-content;会导致ai字幕老是显示2行,还删除了left: 50%; max-width: 70%; opacity: 0.8;
var parentElement2 = document.querySelector('.vp-video__subtitle-text');
//if (parentElement2) {
// parentElement2.style = 'position: absolute; font-size: 20px; padding-top:5px; padding-bottom:25px; height: fit-content; left: 50%; top: 94%; opacity: 0.8;';
parentElement2.style = 'position: absolute; top: 94%; font-size: 20px; padding-top:5px; padding-bottom:25px;';
//}
/* 已经转移到css中
// 隐藏控制栏(包含进度条和播放工具条)(只显示字幕清晰度 全屏三个按钮)
var parentElement4 = document.querySelector('.vp-video__control-bar');
parentElement4.style = 'position: absolute; color: brown; bottom:-15%; ';
var parentElement1 = document.querySelector('.vp-video .vp-video__control-bar--button.is-text');
parentElement1.style = 'color: blue';
var parentElement0 = document.querySelector('.vp-video .vp-video__control-bar--button');
parentElement0.style = 'color: blue';
*/
//(颜色设置见后面)设置当前时间的在屏幕中的上下位置(不能设置; display: block)(若设置left:-1.5%,会导致全屏的时间看不清)
var parentElement5= document.querySelector(".vp-video__control-bar--play-time-current");
//parentElement5.style = 'position: relative;color: yellowgreen; top: 97%; opacity: 1'; //显示在视频中
parentElement5.style = 'position: absolute; right: 50%;top: 100.4%; opacity: 1'; //显示在屏幕下方
//(颜色设置见后面)设置视频总时间的在屏幕中的上下位置(不能设置; display: block)
var parentElement6= document.querySelector(".vp-video__control-bar--play-time-all");
//parentElement6.style = 'position: relative; left:93%;color: yellowgreen; top: 94.5%; opacity: 1';//显示在视频中
parentElement6.style = 'position: relative; left:93%;color: yellowgreen; top: 100.4%; opacity: 1';//显示在屏幕下方
// 设置进度条的颜色为没有颜色(注意style在语句中是style=background,属性中是vs下面就是background)(不能同时搜索多个class)()
// var parentElement13 = document.querySelector('.vp-video__control-bar--play-time');
//parentElement13.style = 'background: 0; color: yellowgreen; opacity: 0';//注释此句后可以看到蓝色进度条
var parentElement14 = document.querySelector("#vjs_video_596 > div.vp-video__control-bar--play-time-all > div");
parentElement14.style = 'background: 0; color: brown; font-size: 15px ; opacity: 1';
var parentElement15 = document.querySelector("#vjs_video_596 > div.vp-video__control-bar--play-time-current > div")
parentElement15.style = 'background: 0; color: brown; font-size: 15px; opacity: 1 ';
/* 已经写入css //设置视频标题的位置和文本大小 //<div class="vp-toolsbar" style="">
var parentElement16 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div.vp-toolsbar")
//parentElement16.style = 'position: absolute; top: -4.6%; left: 50%; transform: translate(-50%, -20%);justify-content: center; font-size: 14px;' // 视频标题显示在顶部 //被css代替
parentElement16.style = 'position: relative; left: 70%; transform: translate(-50%, -20%);justify-content: center; font-size: 14px;' // 视频标题显示在底部 //不用设置:视频标题本来就在底部
var parentElement17 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div.vp-toolsbar > section.vp-toolsbar__title")
parentElement17.style = 'font-size: 14px;'
*/
/* (无效)设置字幕位置
var parentElement20 = document.querySelector("#vjs_video_596 > div.vp-video__subtitle-text")
parentElement20.style = 'height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;' // 视频标题显示在顶部
//#vjs_video_596 > div.vp-video__subtitle-text{height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;}
var parentElement21 = document.querySelector("#vjs_video_596 > div.vp-video__subtitle-text.show")
parentElement21.style = 'height: fit-content;left: 50%;top: 94%;max-width: 70%;opacity: 0.8;' // 视频标题显示在顶部
*/
//移除广告
//案例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");
//var parentElement22 = document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block")
//var parentElement22 = document.querySelectorAll("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block")
var parentElement22 = [
//用于百度网盘,不能颠倒顺序,否则,会导致分享页面的视频的当前时间读取为0:0:0
document.querySelector("#app > div > div.vp-personal-video-main > section > section > section > main > section > div > section.vp-toolsbar__tools-block"),
//用于百度网盘,
document.querySelector(".vjs-time-tooltip"),
//用于bilibili的
document.querySelector(".bpx-player-ctrl-time-current")
];
parentElement22.style = ' display: none;'
/* .vp-header{display: none !important;}没有必要(因为顶部栏取消不了)
.vjs-progress-control{
display: none !important;
}
.video-js .vjs-control-bar 取消视频控制栏的阴影
vp-toolsbar__tools-block 屏蔽分享下载按钮
li.vp-menu-item:nth-of-type(1) 屏蔽标题栏中的消息按钮 //不屏蔽
.ai-course__feedback-container 课件中的反馈符号
.vp-vip-pri 屏蔽视频页中的右下角特权介绍
.vp-personal-aside.vp-aside 屏蔽百度网盘标题栏右侧
*/
if (settings.subtitleAutoEnable) {
setTimeout(() => {
subtitleBtn.click();
(function() {
'use strict';
// 创建一个 MutationObserver 实例-
const observer = new MutationObserver(function(mutationsList) {
for (let mutation of mutationsList) {
// 检查是否有新添加的 ai-draft__wrap-list 子元素
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function(addedNode) {
// 获取 ai-draft__wrap-list 元素
var parentElement = document.querySelector('.ai-draft__wrap-list');
// 设置字幕为可编辑
parentElement.contentEditable = true;
/*已经移到外面去了
// 设置字幕的位置(设置为相对位置,能解决全屏但是不能解决字幕栏的出现因此设置,由于是相对位置可以删除left:30%;)(设置absolute不能设置字幕位置)
//(设置fixed或者absolute,能解决存在字幕栏的问题)(移到外面去字幕没有那么长,不过上下位置不能调节,20240122能调节上下位置)(看清来刺眼:background: #fff; color: #030b1a; opacity: 0.4;)
//parentElement2.style = 'position: relative; display: block; top: 85%; font-size: 15px; opacity: 0.4;'; //这是原来的,会导致字幕栏一行铺满视频
var parentElement2 = document.querySelector('.vp-video__subtitle-text');
parentElement2.style = 'position: absolute; display: block; top: 94%; font-size: 20px; padding-top:5px; padding-bottom:25px';
*/
/* 已经移动到脚本开始处
// 设置进度条的颜色为没有颜色(注意style在语句中是style=background,属性中是vs下面就是background)(不能同时搜索多个class)(青蓝色aqua aquamarine 紫色blueviolet 棕红色brown 黄色burlywood 深绿cadetblue 墨水蓝cornflowerblue 灰色gray 淡绿yellowgreen)
var parentElement3 = document.querySelector('.vjs-control-bar');
parentElement3.style = 'background: 0;opacity: 0.5; position: relative; bottom:1%;';//注释此句后可以看到蓝色进度条
// 隐藏控制栏(包含进度条和播放工具条)
var parentElement4 = document.querySelector('.vp-video__control-bar');
parentElement4.style = 'position: absolute; color: brown; bottom:-15%; ';
var parentElement1 = document.querySelector('.vp-video .vp-video__control-bar--button.is-text');
parentElement1.style = 'color: blue';
var parentElement0 = document.querySelector('.vp-video .vp-video__control-bar--button');
parentElement0.style = 'color: blue';
*/
/*
//(不用管进度条了,调节不了算了) 显示进度条(设为relative为79%)(为了可以避免进度点动态显示设为absolute为79%,但是设置不了位置)
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");
parentElemen7.style = 'position: relative;top:90%; opacity: 1';
// 显示当前进度条-显示蓝色进度
var parentElemen8 = document.querySelector(".vjs-play-progress.vjs-slider-bar");
parentElemen8.style = 'position: relative;top:90%; opacity: 1';
// 显示当前进度条-解锁进度条的点击功能(用了relative,进度条不能拖动)(设为relative为79%)
var parentElemen9 = document.querySelector(".vjs-progress-holder.vjs-slider.vjs-slider-horizontal");
parentElemen9.style = 'position: relative; top:90%; opacity: 1';
// 显示当前进度条-暂停时进度原点自动调到对应位置
//var parentElemen10 = document.querySelector(".vp-video-progress-control");
//var parentElemen10 = document.querySelector(".video-js.vjs-control-bar.vp-video-progress-control.vjs-play-progress:before");
// parentElemen10.style = 'position: relative; top:79%; opacity:1';
// 无效
//var parentElemen11 = document.querySelector(".vp-video-progress-control.vjs-control.vjs-progress-control");
//parentElemen11.style = 'position: relative; top:90%; opacity: 1';
//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");
//parentElemen16.style = 'position: relative; top:90%; opacity: 1';
//var parentElemen17 = document.querySelector(".vp-video-progress-control.vjs-control.vjs-progress-control > .vjs-slider-horizontal.vjs-slider.vjs-progress-holder");
//parentElemen17.style = 'position: relative; top:90%; opacity: 1';
//var parentElemen12 = document.querySelector(".vjs-play-progress .vjs-slider-bar");
//parentElemen12.style = 'position: relative; top:90%; opacity: 1';
*/
// 获取音量控制面板元素
var volumePane2 = document.querySelector('.vjs-volume-panel.vjs-control.vjs-volume-panel-vertical');
// 如果找到了音量控制面板元素,则隐藏它
if (volumePane2) {
volumePane2.style = 'opacity: 1';
volumePane2.style.display = 'none';
}
//获取视频播放器控制栏元素(查询多类,不用空格分隔类)
//var volumePanel = document.querySelectorAll('.vjs-control-bar.video-js');
//无效-如果找到了音量控制面板元素,则隐藏它
// if (volumePanel) {
// volumePanel.style = 'opacity: 0';
//volumePanel.style.display = 'none';
// }
});
}
}
});
// 在整个文档上启动 MutationObserver
observer.observe(document.documentElement, { childList: true, subtree: true });
})();
}, 1500)
}
//FIX: 修改部分css修复宽度小时无法显示全的问题
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'
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()">×</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) {
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 //向文山修改处-在1.35及之前需要注销此句,否则会导致视频倍数插件的c键失效,现在复制文本,可以复制到此处,也可以复制到计时器中
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 = 8
res.login_info.vip_point = 99999
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 = JSON.parse(this.responseText)
res.user_info.is_vip = 1
res.user_info.is_svip = 1
res.user_info.is_plus_buy = 1
Object.defineProperty(this, "responseText", {
writable: true,
});
this.responseText = JSON.stringify(res)
}
})
originOpen.apply(this, arguments);
} else if (url.indexOf('/membership/user') != -1) {
this.addEventListener('readystatechange', function() {
if (this.readyState == 4) {
let res = JSON.parse(this.responseText)
res.reminder = {
"svip": {
"leftseconds": 9999999999,
"nextState": "normal"
}
}
res.level_info = {
"current_value": 12090,
"current_level": 10,
"history_value": 11830,
"history_level": 10,
"v10_id": "666666",
"last_manual_collection_time": 0
}
res.product_infos = [{
"product_id": "",
"start_time": 1685635199,
"end_time": 1888227199,
"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"
}
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.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=${settings.path}&jsToken=${unsafeWindow.jsToken}`
xhr.open("GET", url, false)
xhr.send()
this.responseText = xhr.responseText
} 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)
}
}
})
} 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 {
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(); //向文山修改处 4表示字幕分栏在右侧导航栏中的位置,ps:要全部搜索修改.vp-tabs__header-item:nth-child(2)和(1),共修改5次
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()
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)
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(2)').click();
setTimeout(() => {
document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
}, 500)
}, 2500)
}
}, 500)
})()