Greasy Fork is available in English.
查询b站视频总时长并计算倍速下的观看时间,支持窗口拖动与动态调整显示样式!之前学习尚硅谷Java的时候,总会想要用多长时间才能看完第一到第一百集的视频,然后刚好也学了一些JavaScript,所以立马就动手写了,但是第一版页面惨不忍睹,现在使用ai进一步辅助,美化了整个页面,相信我,小叶查询器会是你在b站学习网课的好帮手!
当前为
// ==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
// ==/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();
})();