Greasy Fork

Greasy Fork is available in English.

小叶的b站视频时间查询器

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

当前为 2024-10-19 提交的版本,查看 最新版本

// ==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();
})();