// ==UserScript== // @name 小叶的b站视频时间查询器 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 查询b站视频总时长并计算倍速下的观看时间,支持窗口拖动与动态调整显示样式!之前学习尚硅谷Java的时候,总会想要用多长时间才能看完第一到第一百集的视频,然后刚好也学了一些JavaScript,所以立马就动手写了,但是第一版页面惨不忍睹,现在使用ai进一步辅助,美化了整个页面,相信我,小叶查询器会是你在b站学习网课的好帮手! // @author 小叶 // @license 小叶 License // @match *://*bilibili.com/* // @match *://www.bilibili.com/video/* // @match *://*.bilibili.com/* // @match *://m.bilibili.com/video/* // @match *://www.bilibili.com/anime/* // @match *://m.bilibili.com/anime/* // @match *://www.bilibili.com/bangumi/play/* // @match *://m.bilibili.com/bangumi/play/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== javascript: (function() { let isPopupVisible = false; let containerOpacity = GM_getValue('containerOpacity', 0.8); const createPopupTrigger = () => { const existingTrigger = document.getElementById('popup-trigger-button'); if (existingTrigger) { existingTrigger.remove(); } const body = document.body; // Add container for both the icon and button const triggerContainer = document.createElement("div"); triggerContainer.id = "popup-trigger-container"; triggerContainer.style.cssText = `position: fixed; right: 20px; top: 8%; z-index: 1000; text-align: center; border: 1px solid #00A1D6; border-radius: 8px; background-color: rgba(255, 255, 255, ${containerOpacity}); padding: 10px;`; // Add icon inside the button container const icon = document.createElement("img"); icon.src = "https://www.bilibili.com/favicon.ico"; icon.alt = "B站图标"; icon.style.cssText = "width: 30px; height: 30px; display: block; margin: 0 auto;"; const triggerButton = document.createElement("button"); triggerButton.id = "popup-trigger-button"; triggerButton.innerText = "小叶计时器"; triggerButton.style.cssText = "background-color: transparent; color: #00A1D6; padding: 10px; border: none; cursor: pointer; font-size: 16px; display: block; margin-top: 10px;"; triggerButton.style.fontWeight = "bold"; triggerButton.onclick = togglePopup; triggerContainer.appendChild(icon); triggerContainer.appendChild(triggerButton); body.appendChild(triggerContainer); }; const togglePopup = () => { isPopupVisible = !isPopupVisible; const triggerButton = document.getElementById('popup-trigger-button'); const triggerContainer = document.getElementById('popup-trigger-container'); triggerButton.innerText = isPopupVisible ? "关闭计时器" : "小叶计时器"; triggerButton.style.color = isPopupVisible ? "#FF0000" : "#00A1D6"; triggerContainer.style.backgroundColor = `rgba(255, 255, 255, ${containerOpacity})`; if (isPopupVisible) { createUI(); } else { closeUI(); } }; const createUI = () => { const existingDiv = document.getElementById('time-calculator-container'); if (existingDiv) { existingDiv.remove(); } const body = document.body; const container = document.createElement("div"); container.id = "time-calculator-container"; container.style.cssText = `padding: 20px; background-color: rgba(255, 255, 255, ${containerOpacity}); position: fixed; right: 20px; top: 20%; width: 280px; max-width: 90%; border-radius: 16px; box-shadow: 0 8px 16px rgba(0,0,0,0.2); border: 1px solid #40E0D0; z-index: 999; text-align: center; font-size: 14px; color: #333; cursor: move;`; // Make the container draggable makeElementDraggable(container); // Title const title = document.createElement("h4"); title.innerText = "小叶的B站时间查询器"; title.style.cssText = "margin-bottom: 20px; color: #00A1D6; font-weight: bold;"; container.appendChild(title); // Input Section const inputDiv = document.createElement("div"); inputDiv.style.cssText = "margin-bottom: 15px; display: flex; justify-content: center; align-items: center;"; const label1 = document.createElement("label"); label1.innerText = "从第"; label1.style.cssText = "margin-right: 5px;"; inputDiv.appendChild(label1); const input1 = document.createElement('input'); input1.type = "number"; input1.style.cssText = "border: 1px solid deepskyblue; width: 50px; text-align: center; margin-right: 5px; padding: 5px; border-radius: 4px;"; input1.min = 1; inputDiv.appendChild(input1); const label2 = document.createElement("label"); label2.innerText = "集 到"; label2.style.cssText = "margin-right: 5px;"; inputDiv.appendChild(label2); const input2 = document.createElement('input'); input2.type = "number"; input2.style.cssText = "border: 1px solid deepskyblue; width: 50px; text-align: center; padding: 5px; border-radius: 4px;"; input2.min = 1; inputDiv.appendChild(input2); container.appendChild(inputDiv); // Speed Input const speedDiv = document.createElement("div"); speedDiv.style.cssText = "margin-bottom: 15px; display: flex; justify-content: center; align-items: center;"; const label3 = document.createElement("label"); label3.innerText = "倍速:"; label3.style.cssText = "margin-right: 5px;"; speedDiv.appendChild(label3); const input3 = document.createElement('input'); input3.type = "number"; input3.style.cssText = "border: 1px solid deepskyblue; width: 60px; text-align: center; padding: 5px; border-radius: 4px; margin-right: 5px;"; input3.value = 1; input3.min = 0.5; input3.step = 0.1; speedDiv.appendChild(input3); const label4 = document.createElement("label"); label4.innerText = " 倍"; speedDiv.appendChild(label4); container.appendChild(speedDiv); // Time Format Selection const formatDiv = document.createElement("div"); formatDiv.style.cssText = "margin-bottom: 20px; display: flex; justify-content: center; align-items: center;"; const formatLabel = document.createElement("label"); formatLabel.innerText = "显示格式:"; formatLabel.style.cssText = "margin-right: 5px;"; formatDiv.appendChild(formatLabel); const formatSelect = document.createElement('select'); formatSelect.style.cssText = "padding: 5px; border-radius: 4px; border: 1px solid deepskyblue;"; const options = ["时分秒", "仅小时", "仅分钟", "仅秒"]; options.forEach(optionText => { const option = document.createElement('option'); option.value = optionText; option.innerText = optionText; formatSelect.appendChild(option); }); formatDiv.appendChild(formatSelect); container.appendChild(formatDiv); // Transparency Slider const transparencyDiv = document.createElement("div"); transparencyDiv.style.cssText = "margin-bottom: 20px; text-align: center;"; const transparencyLabel = document.createElement("label"); transparencyLabel.innerText = "调整透明度:"; transparencyDiv.appendChild(transparencyLabel); const transparencySlider = document.createElement('input'); transparencySlider.type = "range"; transparencySlider.min = 0.1; transparencySlider.max = 1; transparencySlider.step = 0.1; transparencySlider.value = containerOpacity; transparencySlider.style.cssText = "margin-left: 10px;"; transparencySlider.oninput = (e) => { containerOpacity = e.target.value; container.style.backgroundColor = `rgba(255, 255, 255, ${containerOpacity})`; const triggerContainer = document.getElementById('popup-trigger-container'); if (triggerContainer) { triggerContainer.style.backgroundColor = `rgba(255, 255, 255, ${containerOpacity})`; } GM_setValue('containerOpacity', containerOpacity); }; transparencyDiv.appendChild(transparencySlider); container.appendChild(transparencyDiv); // Calculate Button const btn = document.createElement('button'); btn.innerText = "计算时间"; btn.style.cssText = "width: 100%; padding: 12px; border: none; background-color: #00A1D6; color: #FFFFFF; cursor: pointer; border-radius: 8px; font-size: 16px; margin-bottom: 20px;"; btn.onmouseover = () => { btn.style.backgroundColor = "#008BB5"; }; btn.onmouseout = () => { btn.style.backgroundColor = "#00A1D6"; }; btn.onclick = () => calculateTime(formatSelect.value); container.appendChild(btn); // Result Section const resultDiv = document.createElement("div"); resultDiv.id = "resultDiv"; resultDiv.style.cssText = "margin-top: 15px; color: #333; font-weight: bold;"; container.appendChild(resultDiv); // Footer const footer = document.createElement("div"); footer.innerText = "小叶计时器"; footer.style.cssText = "margin-top: 20px; color: #888; font-size: 12px;"; container.appendChild(footer); body.appendChild(container); }; const closeUI = () => { const existingDiv = document.getElementById('time-calculator-container'); if (existingDiv) { existingDiv.remove(); } }; const calculateTime = (format) => { const input1Value = parseInt(document.querySelectorAll('input[type=number]')[0].value); const input2Value = parseInt(document.querySelectorAll('input[type=number]')[1].value); const speedValue = parseFloat(document.querySelectorAll('input[type=number]')[2].value); let hour = 0, minute = 0, second = 0; if (isNaN(input1Value) || isNaN(input2Value) || isNaN(speedValue)) { updateResult("请输入有效的数值。"); return; } if (input1Value < 1 || input2Value < input1Value) { updateResult("小叶tip:输入与实际集数不符。"); return; } const durations = document.getElementsByClassName('duration'); if (durations.length === 0) { updateResult("无法获取视频时长,请确保已加载视频列表。"); return; } for (let i = input1Value - 1; i < input2Value && i < durations.length; i++) { const time = durations[i].innerText; const t = time.match(/\d+/g); let h = 0, m = 0, s = 0; if (t.length === 3) { [h, m, s] = t.map(Number); } else { [m, s] = t.map(Number); } hour += h; minute += m; second += s; } minute += Math.floor(second / 60); second %= 60; hour += Math.floor(minute / 60); minute %= 60; let totalSeconds = (hour * 3600 + minute * 60 + second) / speedValue; const resultHour = Math.floor(totalSeconds / 3600); const resultMinute = Math.floor((totalSeconds % 3600) / 60); const resultSecond = Math.floor(totalSeconds % 60); let resultText = ""; switch (format) { case "时分秒": resultText = `总时长:${resultHour}时${resultMinute}分${resultSecond}秒`; break; case "仅小时": resultText = `总时长:${(totalSeconds / 3600).toFixed(2)} 小时`; break; case "仅分钟": resultText = `总时长:${(totalSeconds / 60).toFixed(2)} 分钟`; break; case "仅秒": resultText = `总时长:${totalSeconds.toFixed(0)} 秒`; break; } updateResult(resultText); }; const updateResult = (text) => { const resultDiv = document.getElementById('resultDiv'); resultDiv.innerText = text; }; const makeElementDraggable = (element) => { let offsetX = 0, offsetY = 0, isDragging = false; element.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - element.getBoundingClientRect().left; offsetY = e.clientY - element.getBoundingClientRect().top; element.style.transition = "none"; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; element.style.left = `${e.clientX - offsetX}px`; element.style.top = `${e.clientY - offsetY}px`; }); document.addEventListener('mouseup', () => { isDragging = false; element.style.transition = "all 0.3s ease"; }); }; createPopupTrigger(); })();