Greasy Fork

Greasy Fork is available in English.

B站合集倒序播放

B站合集倒序播放-脚本

当前为 2024-08-20 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            B站合集倒序播放
// @description     B站合集倒序播放-脚本
// @version         0.0.1
// @author          Grant Howard, Coulomb-G
// @license         MIT
// @match           *://*.bilibili.com/video/*
// @exclude         *://api.bilibili.com/*
// @exclude         *://api.*.bilibili.com/*
// @exclude         *://*.bilibili.com/api/*
// @exclude         *://member.bilibili.com/studio/bs-editor/*
// @exclude         *://t.bilibili.com/h5/dynamic/specification
// @exclude         *://bbq.bilibili.com/*
// @exclude         *://message.bilibili.com/pages/nav/header_sync
// @exclude         *://s1.hdslb.com/bfs/seed/jinkela/short/cols/iframe.html
// @exclude         *://open-live.bilibili.com/*
// @run-at          document-start
// @grant           unsafeWindow
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_info
// @grant           GM_xmlhttpRequest
// @grant           GM_registerMenuCommand
// @grant           GM_unregisterMenuCommand
// @grant           GM_addStyle
// @connect         raw.githubusercontent.com
// @connect         github.com
// @connect         cdn.jsdelivr.net
// @connect         cn.bing.com
// @connect         www.bing.com
// @connect         translate.google.cn
// @connect         translate.google.com
// @connect         localhost
// @connect         *

// @namespace http://greasyfork.icu/users/734541
// ==/UserScript==
(() => {

    GM_addStyle(`#zaizai-div .video-sections-head_second-line {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        margin: 12px 16px 0;
        color: var(--text3);
        color: var(--text3);
        padding-bottom: 12px;
        font-size: 14px;
        line-height: 16px;
        gap: 10px 20px;
    }

    #zaizai-div .border-bottom-line {
        height: 1px;
        background: var(--line_regular);
        margin: 0 15px;
    }

    #zaizai-div .switch-button {
        margin: 0;
        display: inline-block;
        position: relative;
        width: 30px;
        height: 20px;
        border: 1px solid #ccc;
        outline: none;
        border-radius: 10px;
        box-sizing: border-box;
        background: #ccc;
        cursor: pointer;
        transition: border-color .2s, background-color .2s;
        vertical-align: middle;
    }

    #zaizai-div .switch-button.on:after {
        left: 11px;
    }

    #zaizai-div .switch-button:after {
        content: "";
        position: absolute;
        top: 1px;
        left: 1px;
        border-radius: 100%;
        width: 16px;
        height: 16px;
        background-color: #fff;
        transition: all .2s;
    }

    #zaizai-div .switch-button.on {
        border: 1px solid var(--brand_blue);
        background-color: var(--brand_blue);
    }

    #zaizai-div .txt {
        margin-right: 4px;
        vertical-align: middle;
    }
    `)


    const console = (() => {
        const _console = window.console
        return {
            log: (...args) => {
                _console.log(`%c ZAIZAI `,
                    'padding: 2px 1px; border-radius: 3px; color: #fff; background: #42c02e; font-weight: bold;', ...args)
            }
        }
    })()



    // 全局变量
    const local = useReactiveLocalStorage({
        defaultreverseorder: false,
        // 开启倒序播放
        startreverseorder: false
    })

    let Video = null

    function useReactiveLocalStorage(obj) {
        let data = {}
        let zaizaiStore = window.localStorage.getItem('zaizai-store')
        if (zaizaiStore) {
            zaizaiStore = JSON.parse(zaizaiStore)
            for (const key in obj) {
                data[key] = zaizaiStore[key] || obj[key]
            }
        } else {
            data = obj
        }

        let handler = {
            set(target, key, value) {
                let res = Reflect.set(target, key, value)
                try {
                    window.localStorage.setItem(`zaizai-store`, JSON.stringify(data))
                } catch (error) {
                    console.log('存储失败,请检查浏览器设置', error);
                }
                return res
            },
            get(target, key) {
                let ret = Reflect.get(target, key)
                return typeof ret === 'object' ? new Proxy(ret, handler) : ret
            }
        }
        data = new Proxy(data, handler)
        return data
    }

    function waitTime(callback, options = { time: 500, isSetup: false }) {
        let timeout = null
        return new Promise((resolve) => {
            if (options.isSetup) {
                let res = callback()
                if (res) resolve(res)
            }
            timeout = setInterval(() => {
                let res = callback()
                if (res) {
                    clearInterval(timeout)
                    resolve(res)
                }
            }, options.time)
        })
    }

    async function selectVideo() {
        await waitTime(() => {
            let video = document.querySelector('video')
            if (video) {
                Video = video
                return true
            }
        }, {
            isSetup: true
        })
    }

    async function VideoOnPlay() {
        if (local.startreverseorder && !document.querySelector('#zaizai-div')) {
            const div = document.createElement('div')
            div.id = 'zaizai-div'
            div.innerHTML = `
                <div class="video-sections-head">
        <div class="border-bottom-line"></div>
        <div class="video-sections-head_second-line">
            <div>
                <span class="txt">默认开启倒序播放</span>
                <span id="defaultreverseorder" class="switch-button ${local.defaultreverseorder ? 'on' : ''}"></span>
            </div>
            <div>
                <span class="txt">倒序播放</span>
                <span id="startreverseorder" class="switch-button ${local.startreverseorder ? 'on' : ''}"></span>
            </div>
        </div>
    </div>
            `
            const basesections = await waitTime(() => {
                let basev1 = document.querySelector('.base-video-sections-v1')
                if (basev1) {
                    return basev1
                }
            }, { isSetup: true })

            basesections.appendChild(div)

            // 默认开启倒序播放
            let defaultreverseorder = document.querySelector('#defaultreverseorder')
            function defaultSwitchClick() {
                local.defaultreverseorder = !local.defaultreverseorder
                if (local.defaultreverseorder) {
                    this.classList.add('on')
                } else {
                    this.classList.remove('on')
                }
            }
            defaultreverseorder.addEventListener('click', defaultSwitchClick)

            // 倒序播放
            let startreverseorder = document.querySelector('#startreverseorder')
            function switchReverseoOnClick() {
                local.startreverseorder = !local.startreverseorder
                if (local.startreverseorder) {
                    this.classList.add('on')
                    Video.addEventListener('ended', VideoOnEnded)
                } else {
                    this.classList.remove('on')
                    Video.removeEventListener('ended', VideoOnEnded)
                }
            }
            startreverseorder.addEventListener('click', switchReverseoOnClick)

            const button = document.querySelector('.video-sections-head_second-line button').cloneNode()
            button.textContent = '滚动到当前播放'
            button.style.width = '100%'

            function scrollToCurrent() {
                let { currentEl } = getCurrentcard()

                document.querySelector('.video-sections-content-list').scrollTo({
                    top: currentEl.offsetTop - 150
                })
            }
            button.addEventListener('click', scrollToCurrent)
            const newdiv = document.createElement('div')
            newdiv.style.width = '100%'
            newdiv.appendChild(button)
            div.querySelector('.video-sections-head_second-line').appendChild(newdiv)
        }
    }




    function getCurrentcard() {
        const episodecards = document.querySelectorAll('.video-episode-card')
        let i = 0
        for (const element of episodecards) {
            let curicon = element.querySelector('.cur-play-icon')
            if (curicon.style.display !== 'none') {
                break
            }
            i++
        }
        // 顺序上一个
        let previous = i - 1 <= 0 ? episodecards.length - 1 : i - 1
        // 顺序下一个
        let next = i + 1 >= episodecards.length - 1 ? episodecards.length - 1 : i + 1
        return {
            elements: episodecards,
            current: i,
            currentEl: episodecards[i],
            next,
            nextEl: episodecards[next],
            previous,
            previousEl: episodecards[previous]
        }
    }

    function VideoOnEnded() {
        /* let curpage = document.querySelector('.cur-page').textContent
        curpage = curpage.match(/\d+/g).at(-1)
        curpage = parseInt(curpage) */
        const { previousEl } = getCurrentcard()
        previousEl.click()
    }



    async function main() {
        console.log('mian start');

        let is_base_video_sections_v1 = null

        await waitTime(() => {
            let progress = document.querySelector('.bpx-player-progress-schedule-current')
            if (progress) {
                let transform = progress.style.transform.replace('scaleX(', '').replace(')', '')
                if (transform > 0) {
                    is_base_video_sections_v1 = document.querySelector('.base-video-sections-v1')
                    return true
                }
            }
        })

        if (!is_base_video_sections_v1) {
            console.log('mian stop 没有合集');
            return
        }

        await selectVideo()

        Video.addEventListener('play', VideoOnPlay)
        if (local.defaultreverseorder) {
            Video.addEventListener('ended', VideoOnEnded)
        }

        VideoOnPlay()

        console.log('mian stop 成功开启');
    }


    window.onload = () => {
        console.log('正式-v2');
        main()
    }
})()