Greasy Fork

Greasy Fork is available in English.

哔哩哔哩工具[快捷键|视频时长]

快捷键【c】打开关闭弹幕,跳过充电鸣谢,自动下一个,视频时长统计

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         哔哩哔哩工具[快捷键|视频时长]
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  快捷键【c】打开关闭弹幕,跳过充电鸣谢,自动下一个,视频时长统计
// @author       maple
// @match        https://www.bilibili.com/*
// @require      https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js
// @require      https://unpkg.com/[email protected]/log.js
// @icon         https://www.bilibili.com/favicon.ico
// @grant        none
// ==/UserScript==
"use strict";

let logger = Logger.log("debug")

class Handler {

    constructor(videoList) {
        this.videoList = videoList;
    }

    execute() {
        let curLookInx = this.getCurrentLookVideoIndex();

        // 获取所有时间以及未观看时间 的 二维数组表示形式
        let allVideoTimeArr = this.getVideoListTimeArr(0);
        let unLookVideoTimeArr = this.getVideoListTimeArr(curLookInx);

        // 格式化视频总时间数组 [小时,分钟]
        let videoSumTimeArr = this.listTimeArrFormat(allVideoTimeArr);
        // 格式化未看视频时间数组 [小时,分钟]
        let unLookVideoSumTimeArr = this.listTimeArrFormat(unLookVideoTimeArr);

        // 渲染标签
        Handler.renderTimeTag(videoSumTimeArr, unLookVideoSumTimeArr, this.getUnLookVideoCount(curLookInx));
    }

    /**
     * 获取当前正在观看视频索引
     */
    getCurrentLookVideoIndex() {
    }

    /**
     * 获取未观看视频个数
     * @param curIndex 当前观看视频索引
     * @returns {number} 未观看视频个数
     */
    getUnLookVideoCount(curIndex) {
        return this.videoList.length - curIndex;
    }

    /**
     * 获取视频列表时间的二维数组
     * @param index 从索引位置开始获取
     * 格式如下:
     * [
     * 视频1:[小时, 分钟,秒]
     * 视频2:[小时, 分钟,秒]
     * ]
     */
    getVideoListTimeArr(index) {
    }

    /**
     * 视频列表获取到的二维数组时间格式化汇总
     * @param timeTwoArray
     * @returns {(number|number|string)[]} 格式:[小时, 分钟]
     */
    listTimeArrFormat(timeTwoArray) {

        //如果为0没有数据,就出错了
        logger.debug("视频列表长度:" + timeTwoArray.length);

        let h = 0, m = 0, s = 0;
        for (let i = 0; i < timeTwoArray.length; i++) {
            h += Number(timeTwoArray[i][0]);
            m += Number(timeTwoArray[i][1]);
            s += Number(timeTwoArray[i][2]);
        }

        //将秒转换为分钟
        let m1 = Math.floor(s / 60);
        m += m1;

        //分钟转换为小时
        let temp = m / 60;
        let h1 = Math.floor(temp);

        //小于一小时的转换为分钟
        let m2 = ('0.' + String(temp).split('.')[1]) * 60;


        //最终结果
        h += h1;
        m = Math.floor(m2);

        //分钟出现NaN,原因是因为没有分钟,全是小时,直接赋值
        if (isNaN(m)) {
            m = "00";
        }

        logger.debug("小时:" + h);
        logger.debug("分钟:" + m);
        return [h, m];
    }

    /**
     * 渲染时间标签
     * @param all 所有时间一维数组 格式:[小时,分]
     * @param undone 未完成时间一维数组 格式:[小时,分]
     * @param unLookCount 为观看视频个数
     */
    static renderTimeTag(all, undone, unLookCount) {
        //找到显示面板
        let plain = document.getElementsByClassName("video-info-detail")[0];
        try {
            plain.removeChild()
        } catch (e) {
            logger.debug("移除异常")
        }

        let data_tag = document.getElementById("data_tag");

        let isNull = data_tag === null;

        //没有创建过这个标签就创建
        if (isNull) {
            //创建
            data_tag = document.createElement("span");
            logger.debug("创建标签:", data_tag)
            //id赋值,用于下次更新查找
            data_tag.setAttribute("id", "data_tag");
            data_tag.style = '\n' +
                '    background-color: #24c7b4;\n' +
                '    color: white;\n' +
                '    font-size: -0.9rem;\n' +
                '    text-align: center;\n' +
                '    margin-top: 100rem;\n' +
                '    margin-bottom: 100rem;\n' +
                '    padding:0.5rem;\n' +
                '    cursor: pointer;\n' +
                '    border-radius: 1rem;\n' +
                '    ';
        }

        //写入html
        data_tag.innerHTML = show_Str();

        //数据添加到面板
        if (isNull) {
            plain.appendChild(data_tag);
        }


        function show_Str() {
            let all_time = "总时长:" + all.join(' : ');
            let undone_time = "  未观看:" + undone.join(' : ');
            let num = "  未看个数:" + unLookCount;

            return all_time + undone_time + num;
        }
    }
}


/**
 * 合集视频
 */
class HeJiHandler extends Handler {
    getCurrentLookVideoIndex() {
        for (let i = 0; i < this.videoList.length; i++) {
            let videoEle = this.videoList[i];
            if (videoEle.querySelector(".video-episode-card__info-playing")) {
                return i;
            }
        }

        logger.error("未获取到索引")
    }

    getVideoListTimeArr(index) {
        let resultTwoArr = []
        for (let i = index; i < this.videoList.length; i++) {
            let videoEle = this.videoList[i];
            let videoTimeStr = videoEle.querySelector(".video-episode-card__info-duration").innerHTML;

            let child_array = videoTimeStr.split(":");
            if (child_array.length < 3) {
                //数组首部添加0
                child_array.unshift('0');
            }
            resultTwoArr.push(child_array);
        }

        return resultTwoArr;
    }
}

/**
 * 分批视频
 */
class FenPHandler extends Handler {
    getCurrentLookVideoIndex() {
        let index = 0;
        logger.debug("视频列表:", this.videoList)
        for (let i = 0; i < this.videoList.length; i++) {
            //当前观看的视频
            let current = this.videoList[i];
            //延迟之后获取class值

            let class_name = current.className;

            //当前观看
            if (class_name === 'watched on' || class_name === 'on') {
                logger.debug("当前视频索引:" + index)
                return i;
            }
        }
        logger.error("未查询到索引")
        return index;
    }

    getVideoListTimeArr(index) {
        //如果没有这个参数(用于统计总时长)
        if (index === undefined) {
            index = 0;
        }
        let parent_array = [];
        for (let i = index; i < this.videoList.length; i++) {
            let durationDivTag = this.videoList[i].querySelector(".duration");
            logger.debug("单个视频时长标签:", durationDivTag)
            //每个视频的时长
            let duration = durationDivTag.innerHTML;

            //添加到数组
            let child_array = duration.split(":");
            if (child_array.length < 3) {
                //数组首部添加0
                child_array.unshift('0');
            }
            parent_array.push(child_array);

        }
        return parent_array;
    }
}

class HandlerFactor {

    static getHandler(videoType, videoList) {
        if (videoType === 1) {
            return new FenPHandler(videoList);
        } else if (videoType === 2) {
            return new HeJiHandler(videoList);
        }

        throw "视频类型异常";
    }
}

window.onload = function () {
    logger.info("B-Tools运行中")
    // 第一次延迟两秒执行,等待视频列表渲染
    setTimeout(main, 2000)
    // 后续10秒执行,更新时间
    setInterval(main, 10000)
}


function main() {
    let videoType = isLoadedGetVideoType();
    logger.debug("视频类型:" + (videoType === 1 ? "分批视频" : "合集视频"))
    if (!videoType) {
        logger.info("bTools-不是视频列表;结束")
        return;
    }
    let videoList = getVideoList();

    sleepCallbackExecute(200).then(() => {
        HandlerFactor.getHandler(videoType, videoList).execute();
    })
}

function getVideoList() {
    let nodeList = document.querySelector('.list-box');

    // 合集视频
    let nodeList1 = document.querySelector('.video-section-list');

    if (nodeList) {
        return nodeList.childNodes;
    }
    if (nodeList1) {
        return nodeList1.childNodes;
    }
}

function isLoadedGetVideoType() {
    // 分p视频
    if (document.querySelector('.list-box')) {
        return 1;
    }
    // 合集视频
    if (document.querySelector('.video-section-list')) {
        return 2;
    }
}

function sleepCallbackExecute(time) {
    return new Promise((resolve) => setTimeout(resolve, time));
}


////////////////////////////////快捷键///////////////////////////////////////
//注册【c】按钮监听
window.onkeydown = function(ev){
    //ev表示onkeydown事件对象,63为字母 c
    if(ev.keyCode===67){
        let input = document.querySelector(".bui-danmaku-switch-input");
        input.click();
    }
}