// ==UserScript== // @name B站合集倒序播放 // @description 增强B站功能,支持视频合集倒序播放,还有一些其他小功能 // @version 0.0.3 // @author Grant Howard, Coulomb-G // @copyright 2024, Grant Howard // @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 * // @icon https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/images/logo-small.png // @icon64 https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/images/logo.png // @namespace https://greasyfork.org/users/734541 // @downloadURL none // ==/UserScript== ;(() => { GM_addStyle(`.qmsg.qmsg-wrapper { position: fixed; top: calc(50vh - (53px)); left: 0; z-index: 1010; width: 100%; pointer-events: none; color: rgba(0, 0, 0, 0.55); font-size: 13px; font-variant: tabular-nums; font-feature-settings: 'tnum'; } .qmsg .qmsg-item { padding: 8px; text-align: center; animation-duration: 0.3s; } .qmsg .qmsg-item .qmsg-content { text-align: left; position: relative; display: inline-block; padding: 10px 12px; background: #fff; border-radius: 4px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); pointer-events: all; max-width: 80%; min-width: 80px; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] { display: flex; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] .qmsg-icon { display: inline-block; height: 16px; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] .qmsg-icon:first-child { margin-right: 8px; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] .qmsg-icon-close { cursor: pointer; color: rgba(0, 0, 0, 0.45); transition: color 0.3s; margin-left: 6px; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] .qmsg-icon-close:hover > svg path { stroke: #555; } .qmsg .qmsg-item .qmsg-content [class^='qmsg-content-'] .qmsg-count { display: inline-block; position: absolute; left: -8px; top: -8px; color: #fff; font-size: 12px; text-align: center; height: 16px; line-height: 16px; border-radius: 3px; min-width: 16px; animation-duration: 0.3s; } .qmsg .qmsg-item .qmsg-content-info { color: #909399; } .qmsg .qmsg-item .qmsg-content-info .qmsg-count { background-color: #909399; } .qmsg .qmsg-item .qmsg-content-warning { color: #e6a23c; } .qmsg .qmsg-item .qmsg-content-warning .qmsg-count { background-color: #e6a23c; } .qmsg .qmsg-item .qmsg-content-error { color: #f56c6c; } .qmsg .qmsg-item .qmsg-content-error .qmsg-count { background-color: #f56c6c; } .qmsg .qmsg-item .qmsg-content-success { color: #67c23a; } .qmsg .qmsg-item .qmsg-content-success .qmsg-count { background-color: #67c23a; } .qmsg .qmsg-item .qmsg-content-loading { color: #409eff; } .qmsg .qmsg-item .qmsg-content-loading .qmsg-count { background-color: #409eff; } .qmsg .animate-turn { animation: MessageTurn 1s linear infinite; } @keyframes MessageTurn { 0% { transform: rotate(0deg); } 25% { transform: rotate(90deg); } 50% { transform: rotate(180deg); } 75% { transform: rotate(270deg); } 100% { transform: rotate(360deg); } } @keyframes MessageMoveOut { 0% { max-height: 150px; padding: 8px; opacity: 1; } to { max-height: 0; padding: 0; opacity: 0; } } @keyframes MessageMoveIn { 0% { transform: translateY(-100%); transform-origin: 0 0; opacity: 0; } to { transform: translateY(0); transform-origin: 0 0; opacity: 1; } } @keyframes MessageShake { 0%, 100% { transform: translateX(0px); opacity: 1; } 25%, 75% { transform: translateX(-4px); opacity: 0.75; } 50% { transform: translateX(4px); opacity: 0.25; } }`) 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, addsectionslistheigth: false }) let Video = null let videoSections = null let keyupCodeFn = {} 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 switchAddsectionslistheigthOnClick(action) { if (typeof action !== 'string') { local.addsectionslistheigth = !local.addsectionslistheigth } const ListEl = await waitTime( () => { return document.querySelector('.video-sections-content-list') }, { isSetup: true } ) if (local.addsectionslistheigth) { typeof action !== 'string' && this.classList.add('on') ListEl.style.maxHeight = '40vh' ListEl.style.height = '40vh' } else { typeof action !== 'string' && this.classList.remove('on') ListEl.style.height = '150px' ListEl.style.maxHeight = '150px' } } async function getisReverseorder() { const playerloop_checkbox = await waitTime( () => { let checkbo = document.querySelector(`.bui-switch-input[aria-label="洗脑循环"]`) if (checkbo) { return checkbo } }, { isSetup: true } ) return playerloop_checkbox } async function bindWatch() { const playerloop_checkbox = await getisReverseorder() playerloop_checkbox.addEventListener('change', () => { if (playerloop_checkbox.checked) { local.startreverseorder = false document.querySelector('#startreverseorder').classList.remove('on') Video.removeEventListener('ended', VideoOnEnded) } }) } async function VideoOnPlay() { // local.startreverseorder && if (!document.querySelector('#zaizai-div')) { const div = document.createElement('div') div.id = 'zaizai-div' let isstartreverseorder = await getisReverseorder() div.innerHTML = `
默认开启倒序播放
倒序播放
增高合集列表
` videoSections.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') async function switchReverseoOnClick() { const playerloop_checkbox = await getisReverseorder() if (playerloop_checkbox.checked) { Qmsg && Qmsg.warning('请关闭"洗脑循环"后再开启倒序播放') return } local.startreverseorder = !local.startreverseorder if (local.startreverseorder) { startreverseorder.classList.add('on') Video.addEventListener('ended', VideoOnEnded) } else { startreverseorder.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() const sectionsListEl = document.querySelector('.video-sections-content-list') // 42 = currentEl.clientHeight + margin 4 = 列表第一个有4px的margin-top 12是自定义 let scrollToPosition = currentEl.offsetTop - sectionsListEl.clientHeight / 2 - 42 - 4 - 12 sectionsListEl.scrollTo({ top: scrollToPosition }) } 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) let addsectionslistheigth = document.querySelector('#addsectionslistheigth') addsectionslistheigth.addEventListener('click', switchAddsectionslistheigthOnClick) } } 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() } function keyup_key_g() { document.querySelector(`.bpx-player-ctrl-btn[aria-label="网页全屏"]`).click() } keyupCodeFn['g'] = keyup_key_g function keyup_key_h() { document.querySelector(`.bpx-player-ctrl-btn[aria-label="画中画"]`).click() } keyupCodeFn['h'] = keyup_key_h async function main() { console.log('mian start') await waitTime(() => { let progress = document.querySelector('.bpx-player-progress-schedule-current') if (progress) { let transform = progress.style.transform.replace('scaleX(', '').replace(')', '') if (transform > 0) { videoSections = document.querySelector('.base-video-sections-v1') if (!videoSections) { videoSections = document.querySelector('.video-sections-v1') } return true } } }) if (!videoSections) { console.log('mian stop 没有合集') return } await selectVideo() Video.addEventListener('play', VideoOnPlay) if (local.defaultreverseorder) { Video.addEventListener('ended', VideoOnEnded) } await switchAddsectionslistheigthOnClick('0') await VideoOnPlay() await bindWatch() window.addEventListener('keyup', e => { console.log('keyup', e) keyupCodeFn[e.key] && keyupCodeFn[e.key]() }) console.log('mian stop 成功开启') } window.onload = async () => { console.log('正式-v3') let addscript = new Promise(resolve => { const script = document.createElement('script') script.src = 'https://cdn.jsdelivr.net/gh/yaohaixiao/message.js/message.min.js' script.onload = () => { resolve() } document.body.appendChild(script) }) try { await addscript } catch (e) { console.log('添加 message 失败') } console.log('unsafeWindow', unsafeWindow.Qmsg) main().catch(err => { console.log(err) }) } })()