b站视频标记是否看过

b站视频搜索页,标记视频是否看过

目前為 2023-05-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name         b站视频标记是否看过
// @version      2.0
// @author       会飞的蛋蛋面,[email protected]
// @description  b站视频搜索页,标记视频是否看过
// @match        https://search.bilibili.com/*
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM.setValue
// @grant        GM.getValue
// @require      https://code.jquery.com/jquery-3.6.4.slim.min.js

// @namespace https://greasyfork.org/users/751952
// ==/UserScript==

(() => {
    "use strict";

    loading();

    function loading() {
        const url = window.location.href;
        if (url.includes("search.bilibili.com")) {
            loadingObserver(".bili-video-card__image--wrap", () => {
                progressButton();
                videoListListener();
            });
        } else if (url.includes("bilibili.com/video/BV")) {
            loadingObserver('.bpx-player-video-wrap video', videoCurrentTimeListener);
        }
    }

    function loadingObserver(selector, callback) {
        const observer = new MutationObserver(async () => {
            if ($(selector).length !== 0) {
                observer.disconnect();  // 停止监听
                await callback();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });  // 监听 body 元素的子元素变化
    }

    // 添加进度按钮
    async function progressButton() {
        let elementBvMap = new Map();
        for (const element of $(".bili-video-card__image--wrap")) {
            const bv = $(element).parent().parent().attr("href").match(/BV[0-9a-zA-Z]+/);
            if (!bv) {
                continue;
            }
            elementBvMap.set(element, bv[0]);
        }

        let obj = await GM.getValue('videoProgressMap', {});
        const map = new Map(Object.entries(obj)); // 对象转map
        for (const [element, bv] of elementBvMap) {
            let video = map.get(bv)
            if (video != null) {
                let progress = (video.currentTime / video.totalTime).toFixed(2) // 进度百分比
                $("<button>")
                    .attr("id", bv)
                    .addClass("bili-watch-later")
                    .css({
                        left: "8px",
                        width: "40px",
                        height: "20px"
                    })
                    .text(progress >= 0.95 ? "看完" : Math.round(progress * 100) + "%")
                    .appendTo(element);
            }
        }
    }

    // 视频列表监听
    function videoListListener() {
        const debouncedProgressButton = debounce(progressButton, 500);
        $(".mt_sm video-list row").on("DOMSubtreeModified", debouncedProgressButton);
    }

    // 保存视频进度监听
    function videoCurrentTimeListener() {
        const throttledSaveVideoTime = throttle(saveVideoTime, 5000);
        $('.bpx-player-video-wrap video').on("timeupdate", throttledSaveVideoTime);
    }

    // 保存视频进度
    async function saveVideoTime() {
        const bv = window.location.href.match(/BV[0-9a-zA-Z]+/)[0]
        const currentTime = $('video').get(0).currentTime;
        const totalTime = $('video').get(0).duration;

        let obj = await GM.getValue('videoProgressMap', {});
        const map = new Map(Object.entries(obj)); // 对象转map
        map.set(bv, { currentTime, totalTime });
        await GM.setValue('videoProgressMap', Object.fromEntries(map)); // map转对象
    }

    // 防抖
    function debounce(fn, ms) {
        let timerID = null;
        return (...args) => {
            clearTimeout(timerID);
            timerID = setTimeout(() => fn(...args), ms);
        }
    }

    // 节流
    function throttle(fn, ms) {
        let timer = null;
        return (...args) => {
            if (!timer) {
                timer = setTimeout(() => timer = null, ms);
                fn(...args);
            }
        };
    }
})();