Greasy Fork

Greasy Fork is available in English.

B站“稍后再看”按钮

B站新版顶栏中加回“稍后再看”的按钮

当前为 2020-01-20 提交的版本,查看 最新版本

// ==UserScript==
// @id             BilibiliWatchlaterButton@Laster2800
// @name           B站“稍后再看”按钮
// @version        1.1
// @namespace      laster2800
// @author         Laster2800
// @description    B站新版顶栏中加回“稍后再看”的按钮
// @include        *://www.bilibili.com/*
// @include        *://t.bilibili.com/
// @include        *://t.bilibili.com/?spm_id_from*
// @run-at         document-end
// ==/UserScript==

(function() {
    executeAfterElementLoaded('.user-con.signin', null, 100, 5000, addWatchlater)
})();

/**
 * 在条件满足后执行操作
 *
 * @param {Function} condition 指定条件,当 condition() 返回的 result 为真值时执行 callback(result)
 * @param {string} stopCondition 指定终止条件,当 stopCondition() 返回的 stopResult 为真值时终止检测
 * @param {number} interval 检测 condition 和 stopCondition 时间间隔(单位:ms)
 * @param {number} timeout 当检测时间超出该时间后,终止检测(单位:ms)
 * @param {Function} callback 当条件通过时执行 callback(result)
 */
function executeAfterConditionPass(condition, stopCondition, interval, timeout, callback) {
    var cnt = 0
    var maxCnt = timeout / interval
    var tid = setInterval(() => {
        var result = condition()
        var stopResult = stopCondition ? stopCondition() : false
        if (result || stopResult || ++cnt >= maxCnt) {
            clearInterval(tid)
        }
        result && callback(result)
    }, interval)
}

/**
 * 在元素加载完成后执行操作
 *
 * @param {string} selector 该选择器指定一个元素 element,当这个元素加载成功时执行 callback(element)
 * @param {string} stopSelector 该选择器指定一个元素 stopElement,当这个元素加载成功时终止检测
 * @param {number} interval 检测 element 和 stopElement 是否加载成功的时间间隔(单位:ms)
 * @param {number} timeout 当检测时间超出该时间后,终止检测(单位:ms)
 * @param {Function} callback 当 element 加载成功时执行 callback(element)
 */
function executeAfterElementLoaded(selector, stopSelector, interval, timeout, callback) {
    var condition = () => document.querySelector(selector)
    var stopCondition = stopSelector ? () => document.querySelector(stopSelector) : null
    executeAfterConditionPass(condition, stopCondition, interval, timeout, callback)
}

function addWatchlater(header) {
    if (header) {
        var collect = header.children[4]
        var watchlater = header.children[6].cloneNode(true)
        var link = watchlater.firstChild
        link.href = 'https://www.bilibili.com/watchlater/#/list'
        var text = link.firstChild
        text.innerText = '稍后再看'
        header.insertBefore(watchlater, collect)

        // 鼠标移动到“稍后再看”按钮上时,以 Tooltip 形式显示“稍后再看”列表
        var watchlaterPanelSelector = '[role=tooltip][aria-hidden=false] .tabs-panel [title=稍后再看]'
        var dispVue = collect.firstChild.__vue__
        watchlater.onmouseover = () => {
            // 确保原列表完全消失后再显示,避免从“收藏”移动到“稍后再看”时列表反而消失的问题
            executeAfterConditionPass(() => !document.querySelector(watchlaterPanelSelector), null, 10, 500, () => {
                dispVue.showPopper = true
                executeAfterElementLoaded(watchlaterPanelSelector, null, 50, 1500, watchlaterPanel => {
                    watchlaterPanel.parentNode.click()
                })
            })
        }
        // 鼠标从“稍后再看”离开时关闭列表,但移动到“收藏”上面时不关闭
        collect.onmouseover = () => { collect.mouseOver = true }
        collect.onmouseleave = () => { collect.mouseOver = false }
        watchlater.onmouseleave = () => {
            // 要留出足够空间让 collect.mouseOver 变化
            // 但有时候还是会闪,毕竟常规方式估计是无法阻止鼠标移动到“收藏”上时的 Vue 事件
            setTimeout(() => {
                if (!collect.mouseOver) {
                    dispVue.showPopper = false
                }
            }, 100)
        }
    }
}