// ==UserScript== // @name B站合集倒序播放 // @description 增强B站功能,支持视频合集倒序播放,还有一些其他小功能 // @version 0.2.0 // @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 // @require https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/qmsg@1.6.0/dist/index.umd.min.js // @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(`#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; } #zaizai-div .scroll-to-the-current-playback{ cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: var(--brand_blue); color: var(--brand_blue); width: 100%; height: 24px; border-radius: 2px; border: 1px solid var(--brand_blue); border: 1px solid var(--brand_blue); } `); const console = (() => { const _console = window.console; return { log: _console.log.bind( _console, `%c ZAIZAI `, 'padding: 2px 1px; border-radius: 3px; color: #fff; background: #42c02e; font-weight: bold;', ), }; })(); // 全局变量 const $ = window.jQuery; const Qmsg = window.Qmsg; console.log(`🚀 ~ Qmsg:`, Qmsg); if (Qmsg) { Qmsg.config({ position: 'top', }); } 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(); resolve(res); } timeout = setInterval(() => { let res = callback(); if (res) { clearInterval(timeout); resolve(res); } }, options.time); }); } function delayTime(time = 500) { return new Promise((resolve) => { setTimeout(() => { resolve(true); }, time); }); } function waitTask(callback, options = {}) { options = Object.assign({ time: 500, isSetup: false, maxRun: 10 }, options); return new Promise(async (resolve) => { let res; if (options.isSetup) { res = callback(); return resolve(res); } for (let index = 0; index < options.maxRun; index++) { await delayTime(options.time); res = callback(); if (res) { return resolve(res); } } resolve(false); }); } async function selectVideo() { await waitTime( () => { let video = $('video')[0]; console.log(`当前视频元素:`, video); if (video) { Video = video; return true; } }, { isSetup: true, }, ); } // 判断当前聚焦元素是否为表单元素 function isFocusedFormElement() { const activeElement = $(document.activeElement); if (!activeElement.length) return false; const tagName = activeElement.prop('tagName').toLowerCase(); // 常见表单元素标签名 const formTags = ['input', 'textarea', 'select', 'button']; return formTags.includes(tagName); } // 查找多个元素,返回第一个找到的元素 function selectShowEl(arr) { for (const element of arr) { const el = $(element); if (el.length) return el[0]; } } // 对合集列表增高 async function switchAddsectionslistheigthOnClick(action) { if (typeof action === 'boolean') { local.addsectionslistheigth = action; } else { if (this.classList.contains('on')) { local.addsectionslistheigth = false; } else { local.addsectionslistheigth = true; } } const ListEl = await waitTask(() => { return selectShowEl(['.video-sections-content-list', '.rcmd-tab .video-pod__body']); }); if (local.addsectionslistheigth) { $(ListEl).css({ maxHeight: '40vh', height: '40vh', }); $('#addsectionslistheigth').addClass('on'); } else { $(ListEl).removeAttr('style'); $('#addsectionslistheigth').removeClass('on'); } } // 判断是否开启了 循环播放 async function getisReverseorder() { const playerloop_checkbox = await waitTime( () => { let checkbo = selectShowEl([ '.bui-switch-input[aria-label="洗脑循环"]', '.bui-switch-input[aria-label="单集循环"]', ]); if (checkbo) { return checkbo; } }, { isSetup: true }, ); return playerloop_checkbox; } // 如果视频洗脑循环,打开后关闭倒叙播放 async function bindWatch() { const playerloop_checkbox = await getisReverseorder(); if (playerloop_checkbox) { $(playerloop_checkbox).on('change', () => { if (playerloop_checkbox.checked) { local.startreverseorder = false; $('#startreverseorder').removeClass('on'); $(Video).off('ended', VideoOnEnded); } }); } } // 添加功能按钮div async function createControlPanel() { let zaizaiDiv = $('#zaizai-div'); if (zaizaiDiv.length > 0) { return; } const div = $('