您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一个可以记录分P视频进度的脚本
// ==UserScript== // @name B站学习进度记录 // @namespace https://laijiahao.cn/ // @author Mrhuanhao赖佳豪 // @description 一个可以记录分P视频进度的脚本 // @version 2.2 // @grant GM_addStyle // @exclude http://search.bilibili.com/* // @match https://www.bilibili.com/* // @match https://bilibili.com/* // @license MIT // ==/UserScript== function createAPP() { // 创建一个button元素打开box页面 const open_button = document.createElement('button'); open_button.innerHTML = '学习进度'; // 设置按钮文本 open_button.className = 'open-box-button' // 将按钮添加到页面上 document.body.appendChild(open_button); //一个信息气泡框 const displayNotification = function(text){ const notification = document.createElement('div'); notification.textContent = text; notification.className = 'notification' const closeTimeout = 1000; // 1秒后关闭气泡框 document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, closeTimeout); }; let displayVideosList_ = null; // 点击open_button后的页面 const addbox = function () { const box = document.createElement('div'); box.className = 'my-box' //搜素的输入框 const search_input = document.createElement('input'); search_input.className = 'box-search-input' const search_button = document.createElement('button'); search_button.innerHTML = '搜索' search_button.className = 'search-collection-button' const collect_button = document.createElement('button'); collect_button.innerHTML = '收藏' collect_button.className = 'collection-button' const close_box_button = document.createElement('button'); const output_button = document.createElement('button'); const input_button = document.createElement('button') output_button.innerHTML = '导出' output_button.className = 'output-button' input_button.innerHTML = '导入' input_button.className = 'input-button' close_box_button.className = 'close-box-button' //输出函数 const outputInfo = function (){ // 将 collectVideosList 转换为 JSON 字符串 let collectVideosList = JSON.parse(localStorage.getItem('collectVideosList')) || []; const jsonData = JSON.stringify(collectVideosList); // 创建一个带有下载链接的 <a> 元素 const downloadLink = document.createElement('a'); downloadLink.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(jsonData); downloadLink.download = 'collectVideosList.json'; // 模拟 <a> 元素的点击,触发文件下载 downloadLink.click(); } const inputInfo = function (){ const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.json'; fileInput.style.display = 'none'; // 监听文件选择事件 fileInput.addEventListener('change', function(event) { const file = event.target.files[0]; const reader = new FileReader(); reader.onload = function(e) { const importedData = JSON.parse(e.target.result); // 在这里处理导入的 JSON 数据 processImportedData(importedData); }; reader.readAsText(file); document.body.removeChild(fileInput); }); document.body.appendChild(fileInput); fileInput.click(); } // 处理导入的 JSON 数据 const processImportedData = function(data) { console.log('导入的 JSON 数据:', data); localStorage.setItem('collectVideosList',JSON.stringify(data)) displayNotification('导入成功') //先移除本地的列表元素 const videoListContainer = document.querySelector('.my-collection-lists'); if (videoListContainer) { videoListContainer.parentNode.removeChild(videoListContainer); } //展示列表 displayVideosList() } //搜索函数 const searchInfo = function (){ // 获取用户输入的关键字 const keyword = search_input.value.toLowerCase(); // 获取视频列表容器 const videoListContainer = document.querySelector('.my-collection-lists'); // 遍历收藏视频列表 const videoItems = videoListContainer.getElementsByClassName('lists-item'); let searchResult = [] for (let i = 0; i < videoItems.length; i++) { const videoItem = videoItems[i]; const titleElement = videoItem.querySelector('a'); const title = titleElement.innerText.toLowerCase(); // 判断标题是否包含关键字 if (title.includes(keyword)) { searchResult.push(title) // 显示匹配的视频列表项 videoItem.style.display = 'block'; } else { // 隐藏不匹配的视频列表项 videoItem.style.display = 'none'; } } if (searchResult.length === 0) { displayNotification("没有该视频的相关信息") } } //给关闭按钮一个监听事件 -> 关闭box close_box_button.addEventListener('click', function () { box.remove(); }) //给收藏按钮一个监听事件 -> 获取当前信息 collect_button.addEventListener('click', collectVideo) // 添加点击事件监听器 box.appendChild(close_box_button); box.appendChild(search_input); box.appendChild(search_button); box.appendChild(collect_button); box.appendChild(output_button); box.appendChild(input_button); document.body.appendChild(box); // 搜索按钮点击事件处理程序 search_button.addEventListener('click',searchInfo); // 设置按钮点击事件处理程序 output_button.addEventListener('click',outputInfo); input_button.addEventListener('click',inputInfo) //展示视频列表 const displayVideosList = function () { //先去自动获取本地的视频信息 let collectVideosList = JSON.parse(localStorage.getItem('collectVideosList')) || []; //检索一下当前页面的视频 const my_collection_lists = document.createElement('ul'); my_collection_lists.className = 'my-collection-lists' //{title: title, url: video_url, current: current, total: total} //对当前的视频信息进行检索,如果本地缓存存在,就获取信息且显示,否则就不显示 for (let i = 0; i < collectVideosList.length; i++) { let video = collectVideosList[i]; let listItem = document.createElement('li'); listItem.className = 'lists-item'; //视频的标题 listItem.innerHTML = ` <div> <a href ="${video.url}" target="_blank" ">${video.title}</a> </div> `; //创建一个信息容器 let infoContainer = document.createElement('div'); infoContainer.style.display = 'flex'; infoContainer.style.justifyContent = 'space-between'; //视频的进度信息 let progressText = document.createElement('p'); progressText.innerText = `视频进度: ${video.current}/${video.total}`; // 创建移除按钮 let removeButton = document.createElement('button'); removeButton.className = 'remove-button' removeButton.innerText = '移除'; // 在点击移除按钮时执行删除操作 removeButton.addEventListener('click', function () { try { collectVideosList.splice(i, 1); // 从数组中删除当前项 //更新本地存储 localStorage.setItem('collectVideosList', JSON.stringify(collectVideosList)); listItem.remove(); // 从页面中删除当前列表项 displayNotification("删除成功") } catch (error) { displayNotification("删除失败,错误: ", error) } }); // 将视频进度和移除按钮添加到容器元素中 infoContainer.appendChild(progressText); infoContainer.appendChild(removeButton); // 将容器元素添加到列表项中 listItem.appendChild(infoContainer); // listItem.appendChild(removeButton); my_collection_lists.appendChild(listItem); } //把视频列表添加到页面 box.appendChild(my_collection_lists) }; displayVideosList_ = displayVideosList; //页面加载之后去加载一下列表 displayVideosList(); }; open_button.addEventListener('click', addbox); const collectVideo = function () { let collectVideosList = JSON.parse(localStorage.getItem('collectVideosList')) || []; try { //获取视频的信息 let videoInfo = getVideoInfo(); console.log(videoInfo); if (videoInfo.status === "success"){ //判断是否重复 let bvList = [] for (let item of collectVideosList) { // 将 .title 直接添加到数组中 bvList.push(item.bv_id) } // const bv_id = current_url.match(/\/(BV[\w-]+)/)[1]; bvList.push(videoInfo.data.bv_id) if (bvList.length > 0) { // 使用 Set 数据结构进行去重 let uniqueBvLists = new Set(bvList) if (uniqueBvLists.size !== bvList.length) { throw new Error('存在重复') } } // 存储到本地 collectVideosList.push(videoInfo.data); localStorage.setItem('collectVideosList', JSON.stringify(collectVideosList)); // 清空视频列表容器 const videoListContainer = document.querySelector('.my-collection-lists'); if (videoListContainer) { videoListContainer.parentNode.removeChild(videoListContainer); } displayNotification("收藏成功") //去重新加载视频列表 displayVideosList_(); // console.log(collectVideosList) }else{ displayNotification("当前不是分P视频,无法收藏") } } catch (error) { displayNotification("收藏失败") } }; //获取页面的当前的视频信息 点击收藏之后会进行触发 const getVideoInfo = function () { let current = null; let total = null; //先判断当前视频是不是属于分p的 head-left let existing_left = document.getElementsByClassName('head-left') if (existing_left) { let tag_total = existing_left[0].innerText //对括号进行匹配 let tag_total_list = tag_total.match(/\d+/g); //定义集数信息 current = tag_total_list[0] total = tag_total_list[1] } else { console.log("当前不是分P视频"); return { "status": "error", "msg": "当前不是分P视频" } } try { // 获取url let title = document.getElementsByClassName('video-title')[0].innerHTML let current_url = window.location.href; let bv_id = current_url.match(/\/(BV[\w-]+)/)[1]; //对当前的url进行切割判断,先只提取问号前面的基本url let index = current_url.indexOf('?'); let base_url = current_url.substring(0, index); let video_url = base_url + '?p=' + current + '&totalPage=' + total const videoInfo = {bv_id: bv_id ,title: title, url: video_url, current: current, total: total } // 获取本地的视频信息 // let collectVideosList = JSON.parse(localStorage.getItem('collectVideosList')) || []; // collectVideosList.push({ title: title, url: video_url, current: current, total: total }); return { "status": "success", "data": videoInfo, } } catch (error) { displayNotification("当前视频信息获取失败") } }; //监听当前进度是否发生变化 const progressObserve = function (){ // 监听的目标元素为左侧的选集 let targetElement = document.getElementsByClassName('head-left'); // 初始化目标元素的状态 let previousState = targetElement[0].innerText // 定时器间隔时间(毫秒) const interval = 5000; // 定时器回调函数 const checkElementState = function () { const currentState = targetElement[0].innerText // 判断元素状态是否发生变化 if (currentState !== previousState) { console.log(currentState); // 元素状态发生变化 console.log('当前的进度发生变化'); // 去执行更新的操作 updateProgress(); // 更新前一个状态 previousState = currentState; } }; // 获取元素状态的函数,启动计时器 const timerId = setInterval(checkElementState, interval); } //更新视频进度 const updateProgress = function(){ let videoInfo = getVideoInfo(); console.log(videoInfo); //如果不是分P的视频 if (videoInfo.status === "success" ){ let collectVideosList = JSON.parse(localStorage.getItem('collectVideosList')) || []; //只有当bv_id存在的是否才去执行这一个更新的操作 let current_url = window.location.href; const bv_id = current_url.match(/\/(BV[\w-]+)/)[1]; for (let i = 0; i < collectVideosList.length; i++) { item = collectVideosList[i] if (bv_id == item.bv_id){ collectVideosList[i] = videoInfo.data localStorage.setItem('collectVideosList', JSON.stringify(collectVideosList)); displayNotification("视频进度更新成功") } } } } progressObserve(); } function addStyle() { let css = ` .my-box { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; border: 1px solid #ccc; padding: 20px; background-color: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); border-radius: 4px; max-height: 100%; max-width: 400px; width: 90%; } .open-box-button { font-size: 16px; position: fixed; bottom: 20px; right: 20px; padding: 10px; z-index: 9999; background-color: #FF69B4; color: #fff; border: none; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .open-box-button:hover { cursor: pointer; background-color: #ff80bf; } .my-collection-lists{ list-style-type: none; /* 去除默认的列表样式 */ padding: 0; /* 去除列表的内边距 */ margin: 0; /* 去除列表的外边距 */ overflow-y: scroll; max-height:670px; } .notification { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.8); color: white; padding: 10px; border-radius: 5px; z-index: 99999; } .lists-item{ background-color: #f2f2f2; /* 列表项的背景颜色 */ border: 1px solid #ccc; /* 列表项的边框 */ padding: 10px; /* 列表项的内边距 */ margin-bottom: 5px; /* 列表项之间的间距 */ } .remove-button{ background-color: #ff4040; border: none; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); color: white; cursor: pointer; font-size: 10px; font-weight: bold; padding: 5px 7px; transition: background-color 0.3s ease-in-out; } .collection-button, .search-collection-button { padding: 8px 12px; background-color: #0094ec; font-size: 16px; color: #fff; border: none; border-radius: 4px; margin-right: 10px; margin-bottom: 10px; /* 调整按钮之间的垂直间距 */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .collection-button:hover, .search-collection-button:hover { background-color: #00AEEC; cursor: pointer; } .box-search-input { padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; box-shadow: none; outline: none; font-size: 16px; font-family: inherit; margin-right: 10px; /* 调整输入框和按钮之间的水平间距 */ } .box-search-input:focus { border-color: #FF69B4; box-shadow: 0 0 4px #FF69B4; } .output-button { position: absolute; bottom: 0; right: 0; outline: none; font-size: 12px; border: none; background: none; } .output-button:hover{ cursor: pointer; } .input-button { position: absolute; bottom: 0; right: 30px; outline: none; font-size: 12px; border: none; background: none; } .input-button:hover{ cursor: pointer; } .close-box-button { position: absolute; top: 0; right: 0; margin: 8px; width: 26px; height: 26px; border: none; border-radius: 50%; background-color: #ff0000; color: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); display: flex; justify-content: center; align-items: center; } .close-box-button:hover { cursor: pointer; background-color: #ff4040; } .close-box-button::before { content: "X"; font-weight: bold; } ` GM_addStyle(css); } (function () { 'use strict'; addStyle() createAPP() })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址