Greasy Fork

来自缓存

Greasy Fork is available in English.

b站首页推荐

网页端首页推荐视频

当前为 2022-06-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         b站首页推荐
// @namespace    kasw
// @version      2.2
// @description  网页端首页推荐视频
// @author       kaws
// @match        *://www.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @compatible   chrome
// @compatible   firefox
// @compatible   safari

// @source       https://github.com/kawS/bilibili-recommend-app

// @include      https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png?*

// @connect      app.bilibili.com
// @connect      api.bilibili.com
// @connect      passport.bilibili.com
// @connect      www.mcbbs.net

// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_setClipboard
// @run-at       document-idle

// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js

// @license      MIT
// ==/UserScript==

(function() {
  'use strict';
  
  const itemHeight = $('.bili-grid').eq(0).find('.bili-video-card').height();
  let $list = null;
  let isWait = false;
  let isLoading = true;
  let options = {
    clientWidth: $(window).width(),
    sizes: null,
    timeoutKey: 2592000000,
    refresh: 1,
    oneItemHeight: itemHeight,
    listHeight: itemHeight * 4 + 20 * 3,
    accessKey: GM_getValue('biliAppHomeKey'),
    dateKey: GM_getValue('biliAppHomeKeyDate'),
    isShowDanmaku: GM_getValue('biliAppDanmaku') || false,
    isShowRec: GM_getValue('biliAppRec') || false
  }
  function init(){
    if(location.href.startsWith('https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png?')){
      window.stop();
      return window.top.postMessage(location.href, 'https://www.bilibili.com')
    }
    if(location.pathname != '/') return;
    setSize(options.clientWidth, options.isShowRec);
    initStyle();
    intiHtml();
    initEvent();
    checkAccessKey();
    getRecommendList()
  }
  function setSize(width, setRow){
    let row = setRow ? 6 : 4;
    if(width < 1684){
      options.sizes = 5 * row
    }else if(width >= 2183){
      options.sizes = 7 * row
    }else{
      options.sizes = 6 * row
    }
  }
  function initStyle(){
    const style = `
      <style>
        @keyframes turn{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}
        .taglike{position: absolute;bottom: 25px;left: 8px;padding: 0 2px;height: 18px;line-height: 18px;font-size: 12px;}
        .load-state{position: absolute;top: 0;left: 0;background: rgba(0,0,0,.8);width: 100%;height: 100%;min-height: 240px;border-radius: 4px;font-size: 3rem;color: #fff;text-align: center;z-index: 50}
        .load-state .loading{line-height: 240px}
        .load-state .loading svg{margin:0 10px 0 0;width:2rem;height:2rem;transform: rotate(0deg);animation:turn 1s linear infinite;transition: transform .5s ease}
        .toast{position: fixed;top: 30%;left: 50%;z-index: 999999;margin-left: -180px;padding: 12px 24px;font-size: 14px;background: rgba(0,0,0,.8);width: 360px;border-radius: 6px;color: #fff;text-align: center}
        .BBDown{margin-top: 4px;width: 60%;line-height: 1;font-size: 12px;display: inline-block;}
        .v-inline-danmaku{position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 2;pointer-events: none;user-select: none;border-radius: inherit;opacity: 0;transition: opacity .2s linear;overflow: hidden}
        .v-inline-danmaku.visible{opacity: 1}
        .v-inline-danmaku p{position: absolute;color: #fff;text-shadow: #000 1px 0px 1px, #000 0px 1px 1px, #000 0px -1px 1px, #000 -1px 0px 1px;white-space: nowrap;opacity: 0}
        .be-switch-container{position:relative;display:flex;margin:0 0 0 8px;height:20px;cursor:pointer;white-space:nowrap;align-items: center;}
        .be-switch-container.is-checked .be-switch{background-color:#00a1d6}
        .be-switch-container.is-checked .be-switch-cursor{left:17px}
        .be-switch{position:relative;width:30px;height:16px;border-radius:8px;background-color:#ccd0d7;vertical-align:middle;cursor:pointer;transition:background-color .2s ease}
        .be-switch-cursor{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:12px;background:#fff;transition:left .2s ease}
        .be-switch-label{line-height:20px;font-size:14px;margin-left:3px;vertical-align:middle}
        .be-switch-input{position:absolute;left:0;top:0;margin:0;opacity:0;width:100%;height:100%;z-index:2;display: none}
        .bili-video-card .bili-video-card__info--author{display: -webkit-box!important;}
        #recommend .area-header{height: 34px;}
        #recommend .roll-btn-wrap{top: 380px;z-index: 15}
      </style>`;
    $('head').append(style)
  }
  function intiHtml(){
    const $position = $('.bili-grid').eq(1);
    const $fullpage = $('#i_cecream');
    const html = `
      <section class="bili-grid" data-area='Tampermonkey插件推荐' id="recommend">
        <div class="eva-extension-area">
          <div class="area-header">
            <div class="left">
              <a href="javascript:;" class="title"><span>Tampermonkey插件推荐</span></a>
            </div>
            <div class="right">
              <div class="be-switch-container setting-privacy-switcher${options.isShowRec ? ' is-checked': ''}" id="JShowRec">
                <input type="checkbox" class="be-switch-input" value="${options.isShowRec}">
                <div class="be-switch"><i class="be-switch-cursor"></i></div>
                <div class="be-switch-label"><span>是否只保留推荐视频</span></div>
              </div>
              <div class="be-switch-container setting-privacy-switcher${options.isShowDanmaku ? ' is-checked': ''}" id="JShowDanmaku">
                <input type="checkbox" class="be-switch-input" value="${options.isShowDanmaku}">
                <div class="be-switch"><i class="be-switch-cursor"></i></div>
                <div class="be-switch-label"><span>是否预览弹幕</span></div>
              </div>
              <button class="primary-btn roll-btn" id="JaccessKey"}>
                <span>${options.accessKey ? '删除授权' : '获取授权'}</span>
              </button>
              <button class="primary-btn roll-btn"${options.isShowRec ? ' style="display: none"' : ''} id="Jrefresh">
                <svg style="transform: rotate(0deg);"><use xlink:href="#widget-roll"></use></svg>
                <span>换一换</span>
              </button>
            </div>
          </div>
          <div class="eva-extension-body" id="recommend-list"></div>
        </div>
        <div class="roll-btn-wrap"${options.isShowRec ? ' style="display: none"' : ''}>
          <button class="primary-btn roll-btn" id="JrefreshRight">
            <svg style="transform:rotate(0deg);"><use xlink:href="#widget-roll"></use></svg>
            <span>换一换</span>
          </button>
        </div>
      </section>`;
    if($fullpage.length <= 0) return;
    if(options.isShowRec){
      $fullpage.find('.bili-layout').hide().after(`<main class="bili-layout" id="scrollwrap">${html}</main>`);
    }else{
      $position.after(html);
    }
    $list = $('#recommend-list');
  }
  function initEvent(){
    $('#JaccessKey').on('click', function(){
      const $this = $(this);
      let type = $this.text().trim();
      if(isWait) return;
      isWait = true
      if(type == '删除授权'){
        $this.find('span').text('获取授权');
        delAccessKey()
      }
      if(type == '获取授权' || type == '重新获取授权'){
        $this.find('span').text('获取中...');
        getAccessKey($this)
      }
      return false
    })
    $('#Jrefresh, #JrefreshRight').on('click', function(){
      if($('.load-state').length > 0) return;
      const $this = $(this);
      const reg = /(rotate\([\-\+]?((\d+)(deg))\))/i;
      let $svg = $this.find('svg');
      let css = $svg.attr('style');
      let wts = css.match(reg);
      $svg.css('transform', `rotate(${parseFloat(wts[3]) + 360}deg)`);
      options.clientWidth = $(window).width();
      setSize(options.clientWidth);
      getRecommendList();
      return false
    })
    $list.on('mouseenter', '.bili-video-card__image', function(e){
      e.stopPropagation();
      const $this = $(this);
      let rect = e.currentTarget.getBoundingClientRect();
      if($this.data('go') == 'av'){
        $this.find('.bili-watch-later').show();
        $this.find('.v-inline-player').addClass('mouse-in visible');
        getPreviewImage($this, e.clientX - rect.left);
        if(options.isShowDanmaku){
          $this.find('.v-inline-danmaku').addClass('mouse-in visible');
          getPreviewDanmaku($this)
        }
      }
    }).on('mouseleave', '.bili-video-card__image', function(e){
      e.stopPropagation();
      const $this = $(this);
      if($this.data('go') == 'av'){
        $this.find('.bili-watch-later').hide();
        $this.find('.v-inline-player, .v-inline-danmaku').removeClass('mouse-in visible');
      }
    }).on('mousemove', '.bili-video-card__image', function(e){
      e.stopPropagation();
      const $this = $(this);
      let rect = e.currentTarget.getBoundingClientRect();
      if($this.data('go') == 'av'){
        if($this[0].pvData){
          setPosition($this, e.clientX - rect.left, $this[0].pvData)
        }
      }
    }).on('mouseenter', '.bili-watch-later', function(e){
      e.stopPropagation();
      const $this = $(this);
      $this.find('span').show()
    }).on('mouseleave', '.bili-watch-later', function(e){
      e.stopPropagation();
      const $this = $(this);
      $this.find('span').hide()
    }).on('click', '.bili-watch-later', function(){
      const $this = $(this);
      watchlater($this);
      return false
    }).on('click', '.BBDown', function(){
      const $this = $(this);
      let id = $this.data('id');
      GM_setClipboard(`BBDown -app -token ${options.accessKey} -mt -ia -p all "${id}"`);
      toast('复制BBDown命令行成功')
      return false
    })
    $('#JShowDanmaku').on('click', function(){
      const $this = $(this);
      const $inp = $this.find('input');
      let val = JSON.parse($inp.val());
      options.isShowDanmaku = !val;
      GM_setValue('biliAppDanmaku', options.isShowDanmaku);
      $inp.val(options.isShowDanmaku);
      if(options.isShowDanmaku){
        $this.addClass('is-checked')
      }else{
        $this.removeClass('is-checked')
      }
      return false
    })
    $('#JShowRec').on('click', function(){
      const $this = $(this);
      const $inp = $this.find('input');
      let val = JSON.parse($inp.val());
      options.isShowRec = !val;
      GM_setValue('biliAppRec', options.isShowRec);
      $inp.val(options.isShowRec);
      if(options.isShowRec){
        $this.addClass('is-checked')
      }else{
        $this.removeClass('is-checked')
      }
      toast('2秒后刷新页面,请稍后!', function(){
        location.reload()
      })
      return false
    })
    if(options.isShowRec){
      $(window).on('scroll', function(){
        const $this = $(this);
        if(($this.scrollTop() + options.oneItemHeight * 3) > ($(document).height() - $(window).height())){
          if(isLoading) return;
          isLoading = true;
          options.clientWidth = $(window).width();
          setSize(options.clientWidth, true);
          getRecommendList()
        }
      })
    }
  }
  function toast(msg, cb, duration = 2000){
    const $toast = $(`<div class="toast">${msg}</div>`);
    $toast.appendTo($('body'));
    setTimeout(() => {
      $toast.remove();
      typeof cb == 'function' && cb()
    }, duration)
  }
  function showLoading(minHeight){
    $list.prepend(`
      <div class="load-state spread-module" style="height:${minHeight}px">
        <p class="loading" style="line-height:${minHeight / 2}px">
          <svg><use xlink:href="#widget-roll"></use></svg>正在加载...
        </p>
      </div>`)
  }
  function delAccessKey(){
    isWait = false;
    options.accessKey = null;
    options.dateKey = null;
    GM_deleteValue('biliAppHomeKey');
    GM_deleteValue('biliAppHomeKeyDate');
    toast('删除授权成功');
  }
  async function getAccessKey($el){
    let url = null;
    let res = null;
    let data = null;
    try {
      res = await fetch('https://passport.bilibili.com/login/app/third?appkey=27eb53fc9058f8c3&api=https%3A%2F%2Fwww.mcbbs.net%2Ftemplate%2Fmcbbs%2Fimage%2Fspecial_photo_bg.png&sign=04224646d1fea004e79606d3b038c84a', {
        method: 'GET',
        credentials: 'include'
      })
    } catch (error) {
      toast(error)
    }
    try {
      data = await res.json();
    } catch (error) {
      toast(error)
    }
    if (data.code || !data.data) {
      $el.find('span').text('获取授权');
      toast(data.msg || data.message || data.code)
    } else if (!data.data.has_login) {
      $el.find('span').text('获取授权');
      toast('你必须登录B站之后才能使用授权')
    } else if (!data.data.confirm_uri) {
      $el.find('span').text('获取授权');
      toast('无法获得授权网址')
    } else {
      url = data.data.confirm_uri
    }
    if(url == null){
      isWait = false;
      return
    }
    const $iframe = $(`<iframe src='${url}' style="display: none;" />`);
    $iframe.appendTo($('body'));
    let timeout = setTimeout(() => {
      $iframe.remove();
      $el.find('span').text('获取授权');;
      toast('获取授权超时')
    }, 5000);
    window.onmessage = ev => {
      if (ev.origin != 'https://www.mcbbs.net' || !ev.data) {
        isWait = false;
        return
      }
      const key = ev.data.match(/access_key=([0-9a-z]{32})/);
      if (key) {
        GM_setValue('biliAppHomeKey', options.accessKey = key[1]);
        GM_setValue('biliAppHomeKeyDate',  options.dateKey = +new Date());
        toast('获取授权成功');
        $el.find('span').text('删除授权');;
        clearTimeout(timeout);
        $iframe.remove();
      } else {
        toast('没有获得匹配的密钥')
      }
    }
    isWait = false;
  }
  function checkAccessKey(){
    const nowDate = +new Date();
    if(!options.dateKey) return;
    if(options.dateKey == -1){
      $('#JaccessKey').find('span').text('重新获取授权');
      return
    }
    if(nowDate - options.dateKey > options.timeoutKey){
      $('#JaccessKey').find('span').text('重新获取授权');
      GM_setValue('biliAppHomeKeyDate',  options.dateKey = -1);
      GM_deleteValue('biliAppHomeKey');
      options.accessKey = null;
    }
  }
  function getRecommend(url, type){
    const errmsg = '获取推荐视频失败';
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: 'GET',
        url: url,
        onload: res => {
          try {
            const rep = JSON.parse(res.response);
            if (rep.code != 0) {
              reject(errmsg)
            }
            if(type == 'new'){
              resolve(rep.data.item)
            }else{
              resolve(rep.data)
            }
          } catch(e) {
            reject(errmsg)
          }
        },
        onerror: e => {
          reject(errmsg)
        }
      })
    })
  }
  async function getRecommendList(){
    const $listwp = $('#recommend-list');
    if(options.refresh == 1){
      $listwp.height(options.listHeight)
      showLoading(options.listHeight);
    }else{
      if($listwp.attr('style')){
        $listwp.removeAttr('style')
      }
      if(!options.isShowRec){
        showLoading(options.listHeight);
      }
    }
    const token = options.accessKey ? '&access_key=' + options.accessKey : '';
    const url1 = `https://api.bilibili.com/x/web-interface/index/top/rcmd?fresh_type=3&version=1&ps=10&fresh_idx=${options.refresh}&fresh_idx_1h=${options.refresh}`;
    const url2 = 'https://app.bilibili.com/x/feed/index?build=1&mobi_app=android&idx=' + ((Date.now() / 1000).toFixed(0)) + token;
    let result = null;
    let data = null;
    let list = null;
    let isLar = options.sizes - 10 > 10;
    if(options.isShowRec){
      result = Promise.all([getRecommend(url1, 'new'), getRecommend(url2), getRecommend(url1, 'new'), getRecommend(url2), getRecommend(url1, 'new')]);
    }else{
      if(isLar){
        result = Promise.all([getRecommend(url1, 'new'), getRecommend(url2), getRecommend(url1, 'new'), getRecommend(url2)])
      }else{
        result = Promise.all([getRecommend(url1, 'new'), getRecommend(url2)])
      }
    }
    try {
      data = await result;
    } catch (error) {
      toast(error)
    }
    if(options.isShowRec || isLar){
      data[0] = new2old(data[0]);
      data[2] = new2old(data[2]);
      options.isShowRec && (data[4] = new2old(data[4]))
    }else{
      data[0] = new2old(data[0])
    }
    list = unique(data);
    options.refresh += 1;
    updateRecommend(list);
    !$('.bili-footer').is('hidden') && $('.bili-footer').hide()
  }
  function new2old(data){
    return data.map((item) => {
      return {
        autoplay: 1,
        cid: item.cid,
        cover: item.pic,
        ctime: item.pubdate,
        danmaku: item.stat.danmaku,
        desc: `${item.stat.danmaku}弹幕`,
        duration: item.duration,
        face: item.owner.face,
        goto: item.goto,
        idx: item.id,
        like: item.stat.like,
        mid: item.owner.mid,
        name: item.owner.name,
        param: item.id,
        play: item.stat.view,
        title: item.title,
        tname: '',
        uri: item.uri,
        rcmd_reason: {
          content: item.rcmd_reason?.reason_type == 1 ? '已关注' : item.rcmd_reason?.content || ''
        }
      }
    })
  }
  function unique(data){
    const arr = data[0].concat(data[1], data[2] || [], data[3] || [], data[4] || []);
    let result = [];
    let cidList = {};
    for(let item of arr){
      if(!cidList[item.cid]){
        result.push(item);
        cidList[item.cid] = true
      }
    }
    return result.sort(function(){
      return Math.random() - 0.5
    })
  }
  function updateRecommend(list){
    let html = '';
    for(let i=0;i<options.sizes;i++){
      let data = list[i];
      if(!data){
        continue
      }
      html += `
        <div class="bili-video-card" style="display: block !important">
          <div class="bili-video-card__skeleton hide">
            <div class="bili-video-card__skeleton--cover"></div>
            <div class="bili-video-card__skeleton--info">
              <div class="bili-video-card__skeleton--face"></div>
              <div class="bili-video-card__skeleton--right">
                <p class="bili-video-card__skeleton--text"></p>
                <p class="bili-video-card__skeleton--text short"></p>
                <p class="bili-video-card__skeleton--light"></p>
              </div>
            </div>
          </div>
          <div class="bili-video-card__wrap __scale-wrap">
            <a href="${data.goto == 'av' ? 'https://www.bilibili.com/video/av' + data.param : data.uri}" target="${data.goto == 'av' ? 'https://www.bilibili.com/video/av' + data.param : data.uri}" class="cardwp">
              <div class="bili-video-card__image __scale-player-wrap" data-go="${data.goto}" data-aid="${data.param}" data-duration="${data.goto == 'av' ? data.duration : ''}">
                <div class="bili-video-card__image--wrap">
                  <div class="bili-watch-later" data-aid="${data.param}" style="display: none;">
                    <svg class="bili-watch-later__icon"><use xlink:href="#widget-watch-later"></use></svg>
                    <span class="bili-watch-later__tip" style="display: none;">稍后再看</span>
                  </div>
                  <picture class="v-img bili-video-card__cover">
                    <source srcset="${data.cover.replace('http:', 'https:')}@672w_378h_1c_100q.webp" type="image/webp"/>
                    <img src="${data.cover.replace('http:', 'https:')}@672w_378h_1c_100q" alt="${data.title}" loading="eager" onload=""/>
                  </picture>
                  <div class="v-inline-player"></div>
                  <div class="v-inline-danmaku"></div>
                </div>
                <div class="bili-video-card__mask">
                  <div class="taglike" style="background:${data.badge ? ' #ff8f00' : data.tname ? ' #fff' : ' #ff005d'};color:${data.badge ? ' #fff' : data.tname ? ' #333' : ' #fff'};display: none">${data.badge || data.tname || '官方新版推荐'}</div>
                  ${data.badge ? `<div class="taglike" style="background: #ff8f00;color: #fff;">${data.badge}</div>` : data.rcmd_reason && data.rcmd_reason.content == '已关注' ? `<div class="taglike" style="background: #ff8f00;color: #fff;">已关注</div>` : ''}
                  <div class="bili-video-card__stats">
                    <div class="bili-video-card__stats--left">
                      <span class="bili-video-card__stats--item">
                        <svg class="bili-video-card__stats--icon">
                          <use xlink:href="#widget-play-count"></use>
                        </svg>
                        <span class="bili-video-card__stats--text">${formatNumber(data.play)}</span>
                      </span>
                      <span class="bili-video-card__stats--item"${data.goto == 'av' ? '' : ' style="display: none"'}>
                        <svg class="bili-video-card__stats--icon"><use xlink:href="#widget-agree"></use></svg>
                        <span class="bili-video-card__stats--text">${formatNumber(data.like)}</span>
                      </span>
                    </div>
                    <span class="bili-video-card__stats__duration">${data.goto == 'av' ? formatNumber(data.duration, 'time') : formatNumber(data.favorite) + '收藏'}</span>
                  </div>
                </div>
              </div>
            </a>
            <div class="bili-video-card__info __scale-disable">
              <div>
                <a href="https://space.bilibili.com/${data.mid}" target="https://space.bilibili.com/${data.mid}">
                  <div class="v-avatar bili-video-card__avatar">
                    <picture class="v-img v-avatar__face">
                      <source srcset="${data.face.replace('http:', 'https:')}@72w_72h.webp" type="image/webp"/>
                      <img src="${data.face.replace('http:', 'https:')}@72w_72h" alt="${data.name || data.badge}" loading="lazy" onload=""/>
                    </picture>
                  </div>
                </a>
                <a href="javascript:;" class="BBDown" data-id="${data.goto == 'av' ? 'av' + data.param : data.uri}">BBDown下载</a>
              </div>
              <div class="bili-video-card__info--right">
                <a href="${data.goto == 'av' ? 'https://www.bilibili.com/video/av' + data.param : data.uri}" target="${data.goto == 'av' ? 'https://www.bilibili.com/video/av' + data.param : data.uri}">
                  <h3 class="bili-video-card__info--tit" title="${data.title}">${data.title}</h3>
                </a>
                <p class="bili-video-card__info--bottom" style="${(data.rcmd_reason && data.rcmd_reason.content == '已关注') ? 'color: #f00' : data.badge ? 'color: #ff8f00' : ''}">
                  <a class="bili-video-card__info--owner" href="https://space.bilibili.com/${data.mid}" target="https://space.bilibili.com/${data.mid}" ${data.name ? 'title="' + data.name + '"' : ''}>
                    <svg class="bili-video-card__info--owner__up">
                      <use xlink:href="#widget-up"></use>
                    </svg>
                    <span class="bili-video-card__info--author">${data.name || data.badge + ' - ' + data.desc}</span>
                    <span class="bili-video-card__info--date"${data.goto == 'av' ? '' : ' style="display: none"'}>${returnDateTxt(data.ctime)}</span>
                  </a>
                </p>
              </div>
            </div>
          </div>
        </div>`;
    }
    if(options.isShowRec){
      $list.append(html);
      $('.load-state').remove();
      setTimeout(() => {
        isLoading = false
      }, 300)
    }else{
      $list.html(html)
    }
  }
  function formatNumber(input, format = 'number'){
    if (format == 'time') {
      let second = input % 60;
      let minute = Math.floor(input / 60);
      let hour;
      if (minute > 60) {
        hour = Math.floor(minute / 60);
        minute = minute % 60;
      }
      if (second < 10) second = '0' + second;
      if (minute < 10) minute = '0' + minute;
      return hour ? `${hour}:${minute}:${second}` : `${minute}:${second}`
    } else {
      return input > 9999 ? `${(input / 10000).toFixed(1)}万` : input || 0
    }
  }
  function returnDateTxt(time){
    if (!time) return '';
    let diffYear = new Date().getFullYear();
    let date = new Date(time * 1000);
    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();
    return diffYear == year ? 
            `· ${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}` :
            `· ${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`
  }
  function token(){
    try {
      return document.cookie.match(/bili_jct=([0-9a-fA-F]{32})/)[1]
    } catch(e) {
      return ''
    }
  }
  async function watchlater($el){
    const aid = $el.data('aid');
    let type = $el.hasClass('del') ? 'del' : 'add';
    let res = null;
    let data = null;
    try {
      res = await fetch(`https://api.bilibili.com/x/v2/history/toview/${type}`, {
        method: 'POST',
        credentials: 'include',
        headers: {
          'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
        },
        body: `aid=${aid}&csrf=${token()}`
      })
    } catch (error) {
      toast(error)
    }
    try {
      data = await res.json();
    } catch (error) {
      toast(error)
    }
    if(data.code == 0){
      if(type == 'add'){
        $el.addClass('del').find('span').text('移除');
        $el.find('svg').html('<use xlink:href="#widget-watch-save"></use>');
        toast('添加成功')
      }else{
        $el.removeClass('del').find('span').text('稍后再看');
        $el.find('svg').html('<use xlink:href="#widget-watch-later"></use>');
        toast('移除成功')
      }
    }else{
      toast(data.message)
    }
  }
  async function getPreviewImage($el, e){
    const aid = $el.data('aid');
    let pvData = $el[0].pvData;
    if(!pvData){
      let res = null;
      let data = null;
      try {
        res = await fetch(`https://api.bilibili.com/pvideo?aid=${aid}`);
      } catch (error) {
        toast(error)
      }
      try {
        data = await res.json();
      } catch (error) {
        toast(error)
      }
      pvData = $el[0].pvData = data.data;
    }
    setPosition($el, e, pvData)
  }
  async function getPreviewDanmaku($el){
    const aid = $el.data('aid');
    let danmakuData = $el[0].danmakuData;
    if(!danmakuData){
      let res = null;
      let data = null;
      try {
        res = await fetch(`https://api.bilibili.com/x/v2/dm/ajax?aid=${aid}`);
      } catch (error) {
        toast(error)
      }
      try {
        data = await res.json();
      } catch (error) {
        toast(error)
      }
      danmakuData = $el[0].danmakuData = data.data
    }
    setDanmakuRoll($el, danmakuData);
  }
  function setPosition($el, mouseX, pvData){
    const $tarDom = $el.find('.v-inline-player');
    // const $duration = $el.data('duration');
    const $pvbox = $tarDom.find('pv-box');
    const width = $tarDom.width();
    const height = $tarDom.height();
    const sizeX = width * pvData.img_x_len;
    const sizeY = height * pvData.img_y_len;
    const onePageImgs = pvData.img_x_len * pvData.img_y_len;
    const rIndexList = pvData.index.slice(1);
    const pageSize = Math.ceil(rIndexList.length / onePageImgs);
    let percent = mouseX / width;
    if (percent < 0) percent = 0;
    if (percent > 1) percent = 1;
    const durIndex = Math.floor(percent * rIndexList.length)
    const page = Math.floor(durIndex / (pvData.img_x_len * pvData.img_y_len));
    const imgUrl = pvData.image[page];
    const imgIndex = durIndex - page * onePageImgs;
    const x = ((imgIndex - 1) % pvData.img_x_len) * width;
    // const y = Math.floor(imgIndex / (pvData.img_x_len)) * (width * pvData.img_y_size / pvData.img_x_size);
    const y = Math.floor(imgIndex / (pvData.img_x_len)) * height;
    const imgY = (Math.floor(imgIndex / pvData.img_x_len)) + 1;
    const imgX = imgIndex - (imgY - 1) * pvData.img_x_len;
    const bar = percent * 100;
    if($pvbox.length > 0){
      $pvbox.css({
        'background': `url(https:${imgUrl}) -${x}px -${y}px no-repeat`
      })
      $pvbox.next().css({
        'width': `${bat}%`
      })
      return
    }
    $tarDom.html(`
      <div class="pv-box" style="background: url(https:${imgUrl}) -${x}px -${y}px no-repeat;background-size: ${sizeX}px ${sizeY}px;height: 100%;pointer-events:none"></div>
      <div class="pv-bar" style="position: absolute;left: 0;bottom: 0;background: #fb7299;width: ${bar}%;height: 2px;z-index: 2"></div>
    `)
  }
  function setDanmakuRoll($el, danmakuData){
    if(danmakuData.length <= 0) return;
    const $tarDom = $el.find('.v-inline-danmaku');
    let $items = $tarDom.find('p');
    let outWidth = $tarDom.width();
    let lastWait = new Array(5).fill(600);
    let defaultMoveOpts = {
      pageSize: 5,
      size: Math.ceil(danmakuData.length / 5),
      defaultHeight: 18,
      topSalt: 5,
      dur: 5
    }
    if($items.length > 0) $tarDom.empty();
    for(let i = 0;i < danmakuData.length;i++){
      let options = {
        channel: i % defaultMoveOpts.pageSize,
        startPosX: outWidth,
        startPosY: i % 5 * defaultMoveOpts.defaultHeight + defaultMoveOpts.topSalt
      };
      let $html = $(`<p data-channel="${options.channel}" style="top: ${options.startPosY}px;left: ${options.startPosX}px">${danmakuData[i]}</p>`);
      let wait = (lastWait[options.channel] + (Math.floor(Math.random() * 1000 + 100))) / 1000;
      $tarDom.append($html);
      options.width = $html.width();
      options.moveX = options.width + outWidth;
      options.dur = (options.width + outWidth) / (outWidth / defaultMoveOpts.dur);
      $html.css({
        'transform': `translateX(-${options.moveX}px)`,
        'transition': `transform ${options.dur}s linear ${wait}s`,
        'opacity': 1
      })
      lastWait[options.channel] = (wait + options.dur * 0.6) * 1000
    }
  }

  init()
})();