您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
让你自律地看多集视频
当前为
// ==UserScript== // @name B站大学课程辅助器 // @namespace http://tampermonkey.net/ // @version 2.1 // @description 让你自律地看多集视频 // @author zhuangjie // @match https://www.bilibili.com/video/** // @icon https://www.bilibili.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (async function() { 'use strict'; // 【url改变监听器】 function onUrlChange(fun) { let initUrl = window.location.href.split("#")[0]; function urlChangeCheck() { let currentUrl = window.location.href.split("#")[0]; if(initUrl != currentUrl) { console.log("路径改变了") // 新的=>旧的 initUrl = currentUrl; fun(); initUrl = currentUrl; } } let si = setInterval(urlChangeCheck,460) window.onblur = function() { clearInterval(si); } window.onfocus = function() { si = setInterval(urlChangeCheck,460) } } // 数据缓存器 let cache = { get(key) { return GM_getValue(key); }, set(key,value) { GM_setValue(key,value); }, jGet(key) { let value = GM_getValue(key); if( value == null) return value; return JSON.parse(value); }, jSet(key,value) { value = JSON.stringify(value) GM_setValue(key,value); }, remove(key) { GM_deleteValue(key); }, cookieSet(cname,cvalue,exdays) { var d = new Date(); d.setTime(d.getTime()+exdays); var expires = "expires="+d.toGMTString(); document.cookie = cname + "=" + cvalue + "; " + expires; }, cookieGet(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i=0; i<ca.length; i++) { var c = ca[i].trim(); if (c.indexOf(name)==0) return c.substring(name.length,c.length); } return ""; } } // 防抖函数 function debounce(func, delay) { let timeoutId; return function() { const context = this; const args = arguments; clearTimeout(timeoutId); timeoutId = setTimeout(function() { func.apply(context, args); }, delay); }; } // 获取视频的ID function getVideoId() { let regex = /.*?video\/([^?\/]*).*/; // 匹配 /video/ 后面的字符,直到遇到 / let match = window.location.href.match(regex); // 使用正则表达式匹配 if (match && match[1]) { let videoId = match[1]; return videoId; } else { return null; } } // 获取指定属性data-开头的属性名-返回数组 function getDataAttributes(element) { var dataAttributes = []; if (element && element.attributes) { var attributes = element.attributes; for (var i = 0; i < attributes.length; i++) { var attributeName = attributes[i].name; if (attributeName.startsWith('data-')) { dataAttributes.push(attributeName); } } } return dataAttributes; } // 判断当前是否在iframe里面, function currentIsIframe() { if (self.frameElement && self.frameElement.tagName == "IFRAME") return true; if (window.frames.length != parent.frames.length) return true; if (self != top) return true; return false; } // 播放状态修改 function getPlayStatus() { // 播放 true,暂停false var element = document.querySelector('.bpx-player-state-play'); var computedStyle = getComputedStyle(element); var display = computedStyle.getPropertyValue('display'); var visibility = computedStyle.getPropertyValue('visibility'); var isVisible = (display !== 'none' && visibility !== 'hidden'); return !isVisible; } // 修改视频播放状态 function play(isPlay = false) { if(getPlayStatus() == isPlay) return; // 如果状态不一致,让状态一致 var button = document.getElementsByClassName("bpx-player-ctrl-play")[0]; // 创建并初始化一个点击事件 var clickEvent = new MouseEvent("click", { bubbles: true, cancelable: true }); // 派发(click)触发点击事件 button.dispatchEvent(clickEvent); } // 监听某个元素内容变化 let elementChange = { existCheck(select,timeout = 6000) { return new Promise((resolve,reject)=>{ let timer = null; timer = setInterval(()=>{ let element = document.querySelector(select); if( element != null) { resolve(element); clearInterval(timer); }; },100) setTimeout(()=>{clearInterval(timer);},timeout) }) }, hasContentCheck(select,count = 1,timeout = 6000) { return new Promise((resolve,reject)=>{ let timer = null; timer = setInterval(()=>{ let element = document.querySelector(select); let isHasContent = false; if(element == null) return; let innerText = element.innerText; isHasContent = element.childNodes.length >= count && innerText != "" && ! /^\s*<!--[^<>]*-->\s*$/.test(innerText); if( isHasContent ) { resolve(element); clearInterval(timer); } },100) setTimeout(()=>{clearInterval(timer);},timeout) }) } } //=== 脚本主逻辑 ===> let pList = null; let TP_CACHE_KEY = null; let WHEN_SAVING_P_CACHE_KEY = null; let currentEpisodes = null; let controlElement = null; // 视图节点对象 function refreshVideoInfo() { // 刷新是否多集 pList = document.querySelector("#multi_page > div.cur-list > ul"); let oldVideoId = TP_CACHE_KEY; let currentVideoId = TP_CACHE_KEY = getVideoId() WHEN_SAVING_P_CACHE_KEY = TP_CACHE_KEY+":WHEN_SAVING_P_CACHE_KEY" let isVideoChange = oldVideoId != currentVideoId; } // 刷新视频信息 refreshVideoInfo(); // 【程序入口】等待集数目录加载完成-初始化视图 elementChange.hasContentCheck("#multi_page > div.cur-list > ul > li:nth-child(1)").then(()=>{ // 集数目录加载完时,执行初始化视图(如果视图比集数目录显示在前面,可能集数行内容空白) initView(); // url改变时 onUrlChange(()=>{ if(TP_CACHE_KEY == null) return; let videoIdChange = refreshVideoInfo(); let videoPChange = ! window.location.href.includes("p="+currentEpisodes); if( videoPChange || videoIdChange ) { if(pList == null && controlElement != null) { // 多集视频 -> 单视频 执行 controlElement.remove() controlElement = null; }else if( pList != null && controlElement == null){ // 单视频 -> 多集视频时 执行 initView(); return; } // 多集视频时集数切换 执行更新视图变量 if(pList != null) refreshViewState() } }) }) if(pList == null || currentIsIframe() || TP_CACHE_KEY == null) return; // -- 是集合(有集数)的视频 -- // 视图初始化 function initView () { // 之前的集数 let tp = cache.get(TP_CACHE_KEY)??0; let multiPage = document.querySelector("#multi_page"); let inputStyle = ` height: 20px; border-radius: 5px; border: 1.5px solid pink; padding: 2px 5px; box-sizing: border-box; max-width: 60px; ` // 创建新的 <div> 元素 controlElement = document.createElement('div'); // 视图容器样式 controlElement.style = ` margin: 10px 0px; line-height:25px; color:#FB7299; font-weight: 500; ` let dataAttrName = getDataAttributes(document.querySelector("#multi_page"))[0] controlElement.innerHTML = ` <span >当前P<span id="current_episodes">--</span> , 本次目标P</span> <input type="number" style="${inputStyle}" value="${tp}" id="tp_input" ${dataAttrName}="" /> <span id="tp_msg">--</span> ` // 在目标元素前插入新的兄弟元素 multiPage.insertAdjacentElement('beforebegin', controlElement); // 使用防抖修改内容 let tpInput = document.querySelector('#tp_input'); let refresh = debounce(()=>{ // 在这里编写输入值改变事件的处理逻辑 cache.set(TP_CACHE_KEY,parseInt(tpInput.value)); cache.set(WHEN_SAVING_P_CACHE_KEY,currentEpisodes); refreshViewState(); },1500) tpInput.addEventListener('input', ()=>refresh()); refreshViewState(); } // 更新视图状态 async function refreshViewState() { // 当前集数 currentEpisodes= await new Promise((resolve,reject)=>{ let timer = null; timer = setInterval(()=>{ let activeItem = document.querySelector("#multi_page > div.cur-list > ul .watched,.on .page-num") let episodes = null; if(activeItem == null) { clearInterval(timer); resolve(null) return; } episodes = parseInt(activeItem.innerText.replace("P","")) if(episodes != null && episodes >= 1) { clearInterval(timer) resolve(episodes) } },100) }) let tpInput = document.querySelector('#tp_input'); let tp = cache.get(TP_CACHE_KEY)??0; let tpMsg = document.querySelector('#tp_msg'); let currentEpisodesElement = document.querySelector('#current_episodes'); let residueP = tp-currentEpisodes; let whenSavingP = cache.get(WHEN_SAVING_P_CACHE_KEY); let sumP = whenSavingP === undefined?"--":(tp - whenSavingP + 1); let viewed = (typeof sumP === "string")?"--":(sumP - residueP - 1); if( tpInput != null )tpInput.value = tp; currentEpisodesElement.innerHTML = `${currentEpisodes}` let statusMsg = (viewed >= sumP)? (tp == 0?"第一步设置目标!":"太棒了,任务完成了!") :"看完当前+1" ; tpMsg.innerHTML = ` , 进度 ${viewed}/${sumP}集!${statusMsg}`; // 检查 if(tp == 0) return; // 没有设置目标值 if(currentEpisodes >= tp+1) { setTimeout(()=>{ play(false); alert(`你已经达到本次任务!${currentEpisodes > tp+1?"请更新目标":""}`) },100); // 设置状态为暂停 } } // === 扩展功能-暂停与自动播放控制=== (()=>{ // 当页面失去焦点时播放,活动时播放(前提是自动关闭的) let isIntervene = false; document.addEventListener("visibilitychange", function() { if (document.visibilityState === "visible") { // 活动 if(isIntervene) { // 只有干预过,才可自动恢复播放 play(true); isIntervene = false; // 重置为未干预 } } else if(getPlayStatus()){ // 不活动 & 在播放时 isIntervene = true; // 设置为已干预 play(false); } }); })() })();