// ==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 https://greasyfork.org/users/734541 // @downloadURL none // ==/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 = `
默认开启倒序播放
倒序播放
` 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() } })()