B站合集倒序播放

增强B站功能,支持视频合集倒序播放,还有一些其他小功能

// ==UserScript==
// @name            B站合集倒序播放
// @description     增强B站功能,支持视频合集倒序播放,还有一些其他小功能
// @version         0.0.9
// @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
// ==/UserScript==

(() => {
  (function (root, Msg) {
    if (typeof exports === 'object' && typeof module !== 'undefined') {
      module.exports = Msg;
    } else if (typeof define === 'function' && define.amd) {
      define([], function () {
        return Msg(root);
      });
    } else {
      root.Qmsg = Msg(root);
    }
  })(window, function (global) {
    'use strict';

    // assign 兼容处理
    if (typeof Object.assign != 'function') {
      Object.assign = function (target) {
        if (target == null) {
          throw new TypeError('Cannot convert undefined or null to object');
        }

        target = Object(target);
        for (var index = 1; index < arguments.length; index++) {
          var source = arguments[index];
          if (source != null) {
            for (var key in source) {
              if (Object.prototype.hasOwnProperty.call(source, key)) {
                target[key] = source[key];
              }
            }
          }
        }
        return target;
      };
    }

    // 'classList' 兼容处理,add,remove不支持传入多个cls参数
    if (!('classList' in document.documentElement)) {
      Object.defineProperty(HTMLElement.prototype, 'classList', {
        get: function () {
          var self = this;

          function update(fn) {
            return function (value) {
              var classes = self.className.split(/\s+/g),
                index = classes.indexOf(value);
              fn(classes, index, value);
              self.className = classes.join(' ');
            };
          }

          return {
            add: update(function (classes, index, value) {
              if (!~index) classes.push(value);
            }),

            remove: update(function (classes, index) {
              if (~index) classes.splice(index, 1);
            }),

            toggle: update(function (classes, index, value) {
              if (~index) classes.splice(index, 1);
              else classes.push(value);
            }),

            contains: function (value) {
              return !!~self.className.split(/\s+/g).indexOf(value);
            },

            item: function (i) {
              return self.className.split(/\s+/g)[i] || null;
            },
          };
        },
      });
    }

    /**
     * 声明插件名称
     */
    var PLUGIN_NAME = 'qmsg';

    /**
     * 命名空间 用于css和事件
     */
    var NAMESPACE = (global && global.QMSG_GLOBALS && global.QMSG_GLOBALS.NAMESPACE) || PLUGIN_NAME;

    /**
     * 状态 & 动画
     * 显示中,显示完成,关闭中
     */
    var STATES = {
      opening: 'MessageMoveIn',
      done: '',
      closing: 'MessageMoveOut',
    };

    /**
     * 全局默认配置
     * 可在引入js之前通过QMSG_GLOBALS.DEFAULTS进行配置
     * position {String} 位置,仅支持'center','right','left',默认'center'
     * type {String} 类型,支持'info','warning','success','error','loading'
     * showClose {Boolean} 是否显示关闭图标,默认为false不显示
     * timeout {Number} 多久后自动关闭,单位ms,默认2500
     * autoClose {Boolean} 是否自动关闭,默认true,注意在type为loading的时候自动关闭为false
     * content {String} 提示的内容
     * onClose {Function} 关闭的回调函数
     */
    var DEFAULTS = Object.assign(
      {
        position: 'center',
        type: 'info',
        showClose: false,
        timeout: 2500,
        animation: true,
        autoClose: true,
        content: '',
        onClose: null,
        maxNums: 5,
        html: false,
      },
      global && global.QMSG_GLOBALS && global.QMSG_GLOBALS.DEFAULTS,
    );

    /**
     * 设置icon html代码
     */
    var ICONS = {
      info: '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512 64q190.016 4.992 316.512 131.488T960 512q-4.992 190.016-131.488 316.512T512 960q-190.016-4.992-316.512-131.488T64 512q4.992-190.016 131.488-316.512T512 64zm67.008 275.008q26.016 0 43.008-15.488t16.992-41.504-16.992-41.504-42.496-15.488-42.496 15.488-16.992 41.504 16.992 41.504 42.016 15.488zm12 360q0-6.016.992-16T592 664l-52.992 60.992q-8 8.992-16.512 14.016T508 742.016q-8.992-4-8-14.016l88-276.992q4.992-28-8.992-48t-44.992-24q-35.008.992-76.512 29.504t-72.512 72.512v15.008q-.992 10.016 0 19.008l52.992-60.992q8-8.992 16.512-14.016T468 437.024q10.016 4.992 7.008 16l-87.008 276q-7.008 24.992 7.008 44.512T444 800.032q50.016-.992 84-28.992t63.008-72z" fill="#909399"/></svg>',
      warning:
        '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512 64C264.64 64 64 264.64 64 512c0 247.424 200.64 448 448 448 247.488 0 448-200.576 448-448 0-247.36-200.512-448-448-448zm0 704c-26.432 0-48-21.504-48-48s21.568-48 48-48c26.624 0 48 21.504 48 48s-21.376 48-48 48zm48-240c0 26.56-21.376 48-48 48-26.432 0-48-21.44-48-48V304c0-26.56 21.568-48 48-48 26.624 0 48 21.44 48 48v224z" fill="#E6A23C"/></svg>',
      error:
        '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512 64C264.58 64 64 264.58 64 512s200.58 448 448 448 448-200.57 448-448S759.42 64 512 64zm158.39 561.14a32 32 0 1 1-45.25 45.26L512 557.26 398.86 670.4a32 32 0 0 1-45.25-45.26L466.75 512 353.61 398.86a32 32 0 0 1 45.25-45.25L512 466.74l113.14-113.13a32 32 0 0 1 45.25 45.25L557.25 512z" fill="#F56C6C"/></svg>',
      success:
        '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512 64q190.016 4.992 316.512 131.488T960 512q-4.992 190.016-131.488 316.512T512 960q-190.016-4.992-316.512-131.488T64 512q4.992-190.016 131.488-316.512T512 64zm-56 536l-99.008-99.008q-12-11.008-27.488-11.008t-27.008 11.488-11.488 26.496 11.008 27.008l127.008 127.008q11.008 11.008 27.008 11.008t27.008-11.008l263.008-263.008q15.008-15.008 9.504-36.512t-27.008-27.008-36.512 9.504z" fill="#67C23A"/></svg>',
      loading:
        '<svg class="animate-turn" width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" d="M0 0h48v48H0z"/><path d="M4 24c0 11.046 8.954 20 20 20s20-8.954 20-20S35.046 4 24 4" stroke="#409eff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M36 24c0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12" stroke="#409eff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
      close:
        '<svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M14 14L34 34" stroke="#909399" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 34L34 14" stroke="#909399" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
    };

    /**
     * 是否支持动画属性
     * @type {Boolean}
     */
    var CAN_ANIMATION = (function () {
      var style = document.createElement('div').style;
      return (
        style.animationName !== undefined ||
        style.WebkitAnimationName !== undefined ||
        style.MozAnimationName !== undefined ||
        style.msAnimationName !== undefined ||
        style.OAnimationName !== undefined
      );
    })();

    /**
     * 生成带插件名的名称
     * @param {...String}
     * @returns {String}
     */
    function namespacify() {
      var res = NAMESPACE;
      for (var i = 0; i < arguments.length; ++i) {
        res += '-' + arguments[i];
      }
      return res;
    }

    /**
     * 每条消息的构造函数
     * @param {Objetc} opts 配置参数,参考DEFAULTS
     */
    function Msg(opts) {
      var oMsg = this;
      oMsg.settings = Object.assign({}, DEFAULTS, opts || {});
      oMsg.id = Qmsg.instanceCount;
      var timeout = oMsg.settings.timeout;
      timeout = timeout && parseInt(timeout) >= 0 ? parseInt(timeout) : DEFAULTS.timeout;
      oMsg.timeout = timeout;
      oMsg.settings.timeout = timeout;
      oMsg.timer = null;
      var $elem = document.createElement('div');
      var $svg = ICONS[oMsg.settings.type || 'info'];
      var contentClassName = namespacify('content-' + oMsg.settings.type || 'info');
      contentClassName += oMsg.settings.showClose ? ' ' + namespacify('content-with-close') : '';
      var content = oMsg.settings.content || '';
      var $closeSvg = ICONS['close'];
      var $closeIcon = oMsg.settings.showClose ? '<i class="qmsg-icon qmsg-icon-close">' + $closeSvg + '</i>' : '';
      var $span = document.createElement('span');
      if (oMsg.settings.html) {
        $span.innerHTML = content;
      } else {
        $span.innerText = content;
      }
      $elem.innerHTML =
        '<div class="qmsg-content">\
              <div class="' +
        contentClassName +
        '">\
                  <i class="qmsg-icon">' +
        $svg +
        '</i>' +
        $span.outerHTML +
        $closeIcon +
        '</div>\
      </div>';

      $elem.classList.add(namespacify('item'));
      $elem.style.textAlign = oMsg.settings.position;
      var $wrapper = document.querySelector('.' + NAMESPACE);
      if (!$wrapper) {
        $wrapper = document.createElement('div');
        // $wrapper.classList.add(NAMESPACE, namespacify('wrapper'), namespacify('is-initialized'));
        $wrapper.classList.add(NAMESPACE);
        $wrapper.classList.add(namespacify('wrapper'));
        $wrapper.classList.add(namespacify('is-initialized'));
        document.body.appendChild($wrapper);
      }
      $wrapper.appendChild($elem);
      oMsg.$wrapper = $wrapper;
      oMsg.$elem = $elem;
      setState(oMsg, 'opening');
      if (oMsg.settings.showClose) {
        // 关闭按钮绑定点击事件
        $elem.querySelector('.qmsg-icon-close').addEventListener(
          'click',
          function () {
            oMsg.close();
          }.bind($elem),
        );
      }
      $elem.addEventListener(
        'animationend',
        function (e) {
          // 监听动画完成
          var target = e.target,
            animationName = e.animationName;
          if (animationName === STATES['closing']) {
            clearInterval(this.timer);
            this.destroy();
          }
          target.style.animationName = '';
        }.bind(oMsg),
      );
      if (oMsg.settings.autoClose) {
        // 自动关闭
        var intvMs = 10; // 定时器频率
        oMsg.timer = setInterval(
          function () {
            this.timeout -= intvMs;
            if (this.timeout <= 0) {
              clearInterval(this.timer);
              this.close();
            }
          }.bind(oMsg),
          intvMs,
        );
        oMsg.$elem.addEventListener(
          'mouseover',
          function () {
            clearInterval(this.timer);
          }.bind(oMsg),
        );
        oMsg.$elem.addEventListener(
          'mouseout',
          function () {
            if (this.state !== 'closing') {
              // 状态为关闭则不重启定时器
              this.timer = setInterval(
                function () {
                  this.timeout -= intvMs;
                  if (this.timeout <= 0) {
                    clearInterval(this.timer);
                    this.close();
                  }
                }.bind(oMsg),
                intvMs,
              );
            }
          }.bind(oMsg),
        );
      }
    }

    function setState(inst, state) {
      if (!state || !STATES[state]) return;
      inst.state = state;
      inst.$elem.style.animationName = STATES[state];
    }

    /**
     * 直接销毁元素,不会触发关闭回调函数
     */
    Msg.prototype.destroy = function () {
      this.$elem.parentNode && this.$elem.parentNode.removeChild(this.$elem);
      clearInterval(this.timer);
      Qmsg.remove(this.id);
    };
    /**
     * 关闭,支持动画则会触发动画事件
     */
    Msg.prototype.close = function () {
      setState(this, 'closing');
      if (!CAN_ANIMATION) {
        // 不支持动画
        this.destroy();
      } else {
        Qmsg.remove(this.id);
      }
      var callback = this.settings.onClose;
      if (callback && callback instanceof Function) {
        callback.call(this);
      }
    };

    /**
     * 设置消息数量统计
     * @private
     */
    function setMsgCount(oMsg) {
      var countClassName = namespacify('count');
      var $content = oMsg.$elem.querySelector('[class^="qmsg-content-"]'),
        $count = $content.querySelector('.' + countClassName);
      if (!$count) {
        $count = document.createElement('span');
        $count.classList.add(countClassName);
        $content.appendChild($count);
      }
      $count.innerHTML = oMsg.count;
      $count.style.animationName = '';
      $count.style.animationName = 'MessageShake';
      oMsg.timeout = oMsg.settings.timeout || DEFAULTS.timeout;
    }

    /**
     * 合并参数为配置信息,用于创建Msg实例
     * @param {String} txt 文本内容
     * @param {Object} config 配置
     * @private
     */
    function mergeArgs(txt, config) {
      var opts = Object.assign({}, DEFAULTS);
      if (arguments.length === 0) {
        return opts;
      }
      if (txt instanceof Object) {
        return Object.assign(opts, txt);
      } else {
        opts.content = txt.toString();
      }
      if (config instanceof Object) {
        return Object.assign(opts, config);
      }
      return opts;
    }

    /**
     * 通过配置信息 来判断是否为同一条消息,并返回消息实例
     * @param {Object} params 配置项
     * @private
     */
    function judgeReMsg(params) {
      params = params || {};
      var opt = JSON.stringify(params);
      var oInx = -1;
      var oMsg;
      for (var i in this.oMsgs) {
        var oMsgItem = this.oMsgs[i];
        if (oMsgItem.config === opt) {
          oInx = i;
          oMsg = oMsgItem.inst;
          break;
        }
      }
      if (oInx < 0) {
        this.instanceCount++;
        var oItem = {};
        oItem.id = this.instanceCount;
        oItem.config = opt;
        oMsg = new Msg(params);
        oMsg.id = this.instanceCount;
        oMsg.count = '';
        oItem.inst = oMsg;
        this.oMsgs[this.instanceCount] = oItem;
        var len = this.oMsgs.length;
        var maxNums = this.maxNums;
        /**
         * 关闭多余的消息
         */
        if (len > maxNums) {
          var oIndex = 0;
          var oMsgs = this.oMsgs;
          for (oIndex; oIndex < len - maxNums; oIndex++) {
            oMsgs[oIndex] && oMsgs[oIndex].inst.settings.autoClose && oMsgs[oIndex].inst.close();
          }
        }
      } else {
        oMsg.count = !oMsg.count ? 2 : oMsg.count >= 99 ? oMsg.count : oMsg.count + 1;
        setMsgCount(oMsg);
      }
      oMsg.$elem.setAttribute('data-count', oMsg.count);
      return oMsg;
    }

    var Qmsg = {
      version: '0.0.1',
      instanceCount: 0,
      oMsgs: [],
      maxNums: DEFAULTS.maxNums || 5,
      config: function (cfg) {
        DEFAULTS = cfg && cfg instanceof Object ? Object.assign(DEFAULTS, cfg) : DEFAULTS;
        this.maxNums = DEFAULTS.maxNums && DEFAULTS.maxNums > 0 ? parseInt(DEFAULTS.maxNums) : 3;
      },
      info: function (txt, config) {
        var params = mergeArgs(txt, config);
        params.type = 'info';
        return judgeReMsg.call(this, params);
      },
      warning: function (txt, config) {
        var params = mergeArgs(txt, config);
        params.type = 'warning';
        return judgeReMsg.call(this, params);
      },
      success: function (txt, config) {
        var params = mergeArgs(txt, config);
        params.type = 'success';
        return judgeReMsg.call(this, params);
      },
      error: function (txt, config) {
        var params = mergeArgs(txt, config);
        params.type = 'error';
        return judgeReMsg.call(this, params);
      },
      loading: function (txt, config) {
        var params = mergeArgs(txt, config);
        params.type = 'loading';
        params.autoClose = false;
        return judgeReMsg.call(this, params);
      },
      remove: function (id) {
        this.oMsgs[id] && delete this.oMsgs[id];
      },
      closeAll: function () {
        for (var i in this.oMsgs) {
          this.oMsgs[i] && this.oMsgs[i].inst.close();
        }
      },
    };

    return Qmsg;
  });

  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;
    }

    #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: (...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();
        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,
      },
    );
  }

  // 查找多个元素,返回第一个找到的元素
  function selectShowEl(arr) {
    for (const element of arr) {
      const el = document.querySelector(element);
      if (el) return el;
    }
  }

  // 对合集列表增高
  async function switchAddsectionslistheigthOnClick(action) {
    if (typeof action !== 'string') {
      local.addsectionslistheigth = !local.addsectionslistheigth;
    }

    const ListEl = await waitTime(
      () => {
        return selectShowEl(['.video-sections-content-list', '.rcmd-tab .video-pod__body']);
      },
      { 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 = '300px';
    }
  }

  // 判断是否开启了 循环播放
  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.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();
      let isplayerloop = isstartreverseorder?.checked;

      if (isplayerloop) {
        local.defaultreverseorder = false;
        local.startreverseorder = false;
      } else if (local.defaultreverseorder) {
        local.startreverseorder = true;
      }

      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>
                <span class="txt">增高合集列表</span>
                <span id="addsectionslistheigth" class="switch-button ${
                  local.addsectionslistheigth ? 'on' : ''
                }"></span>
            </div>
        </div>
    </div>
            `;

      videoSections.appendChild(div);
      console.log(videoSections);

      // 默认开启倒序播放
      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.createElement('div');
      button.textContent = '滚动到当前播放';
      button.classList.add('scroll-to-the-current-playback');
      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);
    }
  }

  async function getCurrentcard() {
    const episodecards = await waitTime(
      () => {
        // 2024 的B站列表
        let els = document.querySelectorAll('.video-episode-card');
        if (els.length) {
          return els;
        }
        //  2025-1-27 的B站列表
        let body = document.querySelector('.video-pod__body');
        if (body) {
          return body.querySelectorAll('.video-pod__item');
        }
      },
      {
        isSetup: true,
      },
    );

    let i = 0;
    for (const element of episodecards) {
      let curicon = element.querySelector('.playing-gif') || element.querySelector('.playing-gif');
      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;
    const result = {
      elements: episodecards,
      current: i,
      currentEl: episodecards[i],
      next,
      nextEl: episodecards[next],
      previous,
      previousEl: episodecards[previous],
    };
    console.log(result);

    return result;
  }

  async function VideoOnEnded() {
    /* let curpage = document.querySelector('.cur-page').textContent
            curpage = curpage.match(/\d+/g).at(-1)
            curpage = parseInt(curpage) */
    const { previousEl } = await getCurrentcard();
    previousEl.querySelector('.simple-base-item').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(i = 0) {
    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 = selectShowEl(['.base-video-sections-v1', '.video-pod.video-pod']);
          if (videoSections) {
            return true;
          }
          return false;
        }
      }
    });

    if (!videoSections && i < 10) {
      console.log('mian stop 没有合集');
      return main(i++);
    }

    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 成功开启');

    console.log('main stop 检查开启');

    setTimeout(() => {
      if (!document.querySelector('#zaizai-div') && i < 10) {
        i++;
        return main(i + 1);
      } else {
        console.log('mian stop 检查完成 已有');
      }
    }, 500);
  }

  window.onload = async () => {
    console.log('正式-v3');

    /* try {
      await 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);
      });
    } catch (e) {
      console.log('添加 message 失败');
    }
    console.log('unsafeWindow', unsafeWindow.Qmsg); */

    main().catch((err) => {
      console.log(err);
    });
  };
})();