Greasy Fork

Greasy Fork is available in English.

B站封面获取

B站视频、番剧播放页、直播间添加获取封面的按钮

当前为 2020-03-30 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @id             BilibiliCover@Laster2800
// @name           B站封面获取
// @version        3.0
// @namespace      laster2800
// @author         Laster2800
// @description    B站视频、番剧播放页、直播间添加获取封面的按钮
// @include        *://www.bilibili.com/video/*
// @include        *://www.bilibili.com/bangumi/play/*
// @include        *://live.bilibili.com/*
// @exclude        *://live.bilibili.com/
// @exclude        *://live.bilibili.com/p/html/live-web-mng/*
// @grant          GM_addStyle
// ==/UserScript==

(function() {
    if (/\/video\//.test(location.href)) {
        executeAfterConditionPass({
            condition: () => {
                var app = document.querySelector('#app')
                var vueLoad = app && app.__vue__
                if (!vueLoad) {
                    return false
                }
                return document.querySelector('#arc_toolbar_report')
            },
            callback: addVideoBtn,
        })
    } else if (/\/bangumi\/play\//.test(location.href)) {
        executeAfterConditionPass({
            condition: () => {
                var app = document.querySelector('#app')
                var vueLoad = app && app.__vue__
                if (!vueLoad) {
                    return false
                }
                return document.querySelector('#toolbar_module')
            },
            callback: addBangumiBtn,
        })
    } else if (/live\.bilibili\.com\/\d/) {
       executeAfterConditionPass({
           condition: () => {
               var hiVm = document.querySelector('#head-info-vm')
               var vueLoad = hiVm && hiVm.__vue__
               if (!vueLoad) {
                   return false
               }
               return hiVm.querySelector('.room-info-down-row')
           },
           callback: addLiveBtn,
       })
    }
})()

function addVideoBtn(atr) {
    var coverMeta = document.querySelector('head meta[itemprop=image]')
    var coverUrl = coverMeta && coverMeta.content
    var cover = document.createElement('a')
    var errorMsg = '获取失败,若非网络问题请提供反馈'
    cover.innerText = '获取封面'
    cover.onclick = () => coverUrl ? window.open(coverUrl) : alert(errorMsg)
    cover.title = coverUrl || errorMsg
    cover.className = 'appeal-text'
    atr.appendChild(cover)
}

function addBangumiBtn(tm) {
    GM_addStyle(`
    .cover_btn {
        float: right;
        cursor: pointer;
        font-size: 12px;
        margin-right: 16px;
        line-height: 36px;
        color: #505050;
    }
    .cover_btn:hover {
        color: #00a1d6;
    }`)
    var coverMeta = document.querySelector('head meta[property="og:image"]')
    var coverUrl = coverMeta && coverMeta.content
    var cover = document.createElement('a')
    var errorMsg = '获取失败,若非网络问题请提供反馈'
    cover.innerText = '获取封面'
    cover.onclick = () => coverUrl ? window.open(coverUrl) : alert(errorMsg)
    cover.title = coverUrl || errorMsg
    cover.className = 'cover_btn'
    tm.appendChild(cover)
}

function addLiveBtn(ridr) {
    GM_addStyle(`
     .cover_btn {
        cursor: pointer;
        margin-left: 18px;
        color: rgb(153, 153, 153);
    }
    .cover_btn:hover {
        color: #23ade5;
    }`)
    var nw = __NEPTUNE_IS_MY_WAIFU__ // 此window非彼window,不能通过window获取
    var bir = nw && nw.baseInfoRes
    var data = bir && bir.data
    var coverUrl = data && data.user_cover
    var kfUrl = data && data.keyframe
    var cover = document.createElement('a')
    cover.innerText = '获取封面'
    cover.target = '_blank'
    if (coverUrl) {
        cover.href = coverUrl
        cover.title = coverUrl
    } else if (kfUrl) {
        cover.href = kfUrl
        cover.title = '直播间没有设置封面,或者因不明原因无法获取到封面,点击获取关键帧:\n' + kfUrl
    } else {
        var errorMsg = '获取失败,若非网络问题请提供反馈'
        cover.onclick = () => alert(errorMsg)
        cover.title = errorMsg
    }
    cover.className = 'cover_btn'
    ridr.appendChild(cover)
}

/**
 * 在条件满足后执行操作
 *
 * 当条件满足后,如果不存在终止条件,那么直接执行 callback(result)。
 *
 * 当条件满足后,如果存在终止条件,且 stopTimeout 大于 0,则还会在接下来的 stopTimeout 时间内判断是否满足终止条件,称为终止条件的二次判断。
 * 如果在此期间,终止条件通过,则表示依然不满足条件,故执行 stopCallback() 而非 callback(result)。
 * 如果在此期间,终止条件一直失败,则顺利通过检测,执行 callback(result)。
 *
 * @param {Object} [options={}] 选项
 * @param {Function} [options.condition] 条件,当 condition() 返回的 result 为真值时满足条件
 * @param {Function} [options.callback] 当满足条件时执行 callback(result)
 * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
 * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
 * @param {Function} [options.onTimeout] 检测超时时执行 onTimeout()
 * @param {Function} [options.stopCondition] 终止条件,当 stopCondition() 返回的 stopResult 为真值时终止检测
 * @param {Function} [options.stopCallback] 终止条件达成时执行 stopCallback()(包括终止条件的二次判断达成)
 * @param {number} [options.stopInterval=50] 终止条件二次判断期间的检测时间间隔(单位:ms)
 * @param {number} [options.stopTimeout=0] 终止条件二次判断期间的检测超时时间(单位:ms)
 */
function executeAfterConditionPass(options) {
    var defaultOptions = {
        condition: () => true,
        callback: result => console.log(result),
        interval: 100,
        timeout: 5000,
        onTimeout: null,
        stopCondition: null,
        stopCallback: null,
        stopInterval: 50,
        stopTimeout: 0,
    }
    var o = { ...defaultOptions, ...options }
    if (!o.callback instanceof Function) {
        return
    }

    var cnt = 0
    var maxCnt = o.timeout / o.interval
    var tid = setInterval(() => {
        var result = o.condition()
        var stopResult = o.stopCondition && o.stopCondition()
        if (stopResult) {
            clearInterval(tid)
            o.stopCallback instanceof Function && o.stopCallback()
        } else if (++cnt > maxCnt) {
            clearInterval(tid)
            o.onTimeout instanceof Function && o.onTimeout()
        } else if (result) {
            clearInterval(tid)
            if (o.stopCondition && o.stopTimeout > 0) {
                executeAfterConditionPass({
                    condition: o.stopCondition,
                    callback: o.stopCallback,
                    interval: o.stopInterval,
                    timeout: o.stopTimeout,
                    onTimeout: () => o.callback(result)
                })
            } else {
                o.callback(result)
            }
        }
    }, o.interval)
}

/**
 * 在元素加载完成后执行操作
 *
 * 当元素加载成功后,如果没有设置终止元素选择器,那么直接执行 callback(element)。
 *
 * 当元素加载成功后,如果没有设置终止元素选择器,且 stopTimeout 大于 0,则还会在接下来的 stopTimeout 时间内判断终止元素是否加载成功,称为终止元素的二次加载。
 * 如果在此期间,终止元素加载成功,则表示依然不满足条件,故执行 stopCallback() 而非 callback(element)。
 * 如果在此期间,终止元素加载失败,则顺利通过检测,执行 callback(element)。
 *
 * @param {Object} [options={}] 选项
 * @param {Function} [options.selector] 该选择器指定要等待加载的元素 element
 * @param {Function} [options.callback] 当 element 加载成功时执行 callback(element)
 * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
 * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
 * @param {Function} [options.onTimeout] 检测超时时执行 onTimeout()
 * @param {Function} [options.stopCondition] 该选择器指定终止元素 stopElement,若该元素加载成功则终止检测
 * @param {Function} [options.stopCallback] 终止元素加载成功后执行 stopCallback()(包括终止元素的二次加载)
 * @param {number} [options.stopInterval=50] 终止元素二次加载期间的检测时间间隔(单位:ms)
 * @param {number} [options.stopTimeout=0] 终止元素二次加载期间的检测超时时间(单位:ms)
 */
function executeAfterElementLoad(options) {
    var defaultOptions = {
        selector: '',
        callback: el => console.log(el),
        interval: 100,
        timeout: 5000,
        onTimeout: null,
        stopSelector: null,
        stopCallback: null,
        stopInterval: 50,
        stopTimeout: 0,
    }
    var o = { ...defaultOptions, ...options }
    executeAfterConditionPass({
        ...o,
        condition: () => document.querySelector(o.selector),
        stopCondition: o.stopSelector && (() => document.querySelector(o.stopSelector)),
    })
}