B站视频时长计算器

计算B站视频列表中到指定集数的剩余时长

当前为 2025-01-25 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         B站视频时长计算器
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  计算B站视频列表中到指定集数的剩余时长
// @author       Your name
// @match        https://www.bilibili.com/video/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 将时长从MM:SS转换为秒
    function parseDuration(duration) {
        const [minutes, seconds] = duration.split(':').map(Number);
        return minutes * 60 + seconds;
    }

    // 将总秒数转换为HH:MM:SS格式
    function formatDuration(totalSeconds) {
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    }

    // 获取当前播放的集数
    function getCurrentEpisode() {
        const urlParams = new URLSearchParams(window.location.search);
        const p = urlParams.get('p');
        return p ? parseInt(p) : 1;
    }

    // 主要计算逻辑
    function calculateTotalDuration(currentPage) {
        const videoItems = document.querySelectorAll('.duration');
        let totalSeconds = 0;
        let totalEpisodes = videoItems.length;

        videoItems.forEach((item, index) => {
            const pageNum = index + 1;
            if (pageNum >= currentPage) {
                const duration = item.textContent.trim();
                totalSeconds += parseDuration(duration);
            }
        });

        return {
            duration: formatDuration(totalSeconds),
            totalEpisodes: totalEpisodes
        };
    }

    // 创建控件
    function createControls() {
        const container = document.createElement('div');
        container.style.cssText = `
            display: inline-flex;
            gap: 10px;
            align-items: center;
            margin-left: 20px;
            vertical-align: middle;
        `;

        // 创建计算按钮(左)
        const button = document.createElement('button');
        button.textContent = '计算剩余时长';
        button.style.cssText = `
            padding: 5px 10px;
            background: #00A1D6;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
            order: 1;
        `;

        // 创建输入框(中)
        const input = document.createElement('input');
        input.type = 'number';
        input.placeholder = '目标集数';
        input.style.cssText = `
            width: 80px;
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 3px;
            font-size: 14px;
            text-align: center;
            order: 2;
        `;

        // 创建结果显示框(右)
        const resultBox = document.createElement('div');
        resultBox.style.cssText = `
            display: none;
            padding: 5px 10px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            border-radius: 3px;
            font-size: 14px;
            line-height: 1.5;
            white-space: nowrap;
            vertical-align: middle;
            order: 3;
        `;

        container.appendChild(button);
        container.appendChild(input);
        container.appendChild(resultBox);

        const titleContainer = document.querySelector('.video-info-title-inner');
        if (titleContainer) {
            titleContainer.appendChild(container);
        }

        return { button, input, resultBox };
    }

    // 更新显示
    function updateDisplay(resultBox, input) {
        const currentPage = parseInt(input.value) || getCurrentEpisode();
        const result = calculateTotalDuration(currentPage);
        resultBox.textContent = `从第${currentPage}集到最后还有: ${result.duration}`;
        resultBox.style.display = 'block';
    }

    // 初始化
    function init() {
        // 等待页面加载完成
        setTimeout(() => {
            const { button, input, resultBox } = createControls();

            // 设置初始值并显示
            input.value = getCurrentEpisode();
            updateDisplay(resultBox, input);

            // 点击按钮更新
            button.addEventListener('click', () => {
                updateDisplay(resultBox, input);
            });

            // 回车更新
            input.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    button.click();
                }
            });

            // URL变化监听
            let lastUrl = location.href;
            new MutationObserver(() => {
                const url = location.href;
                if (url !== lastUrl) {
                    lastUrl = url;
                    setTimeout(() => {
                        input.value = getCurrentEpisode();
                        updateDisplay(resultBox, input);
                    }, 1000);
                }
            }).observe(document, { subtree: true, childList: true });

            // 视频切换监听
            const videoList = document.querySelector('.video-episode-card');
            if (videoList) {
                new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        if (mutation.target.classList.contains('video-episode-card__info-playing')) {
                            input.value = getCurrentEpisode();
                            updateDisplay(resultBox, input);
                        }
                    });
                }).observe(videoList.parentElement, {
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['class']
                });
            }
        }, 2000);
    }

    // 启动
    window.addEventListener('load', init);
})();