bilibili时间线筛选——分组查看b站动态(布局优化,修复失效,原脚本作者hi94740)

这个脚本能帮你通过关注分组筛选b站时间线上的动态 (布局优化,排除展示番剧,避免无人直播时失效)

// ==UserScript==
// @name bilibili时间线筛选——分组查看b站动态(布局优化,修复失效,原脚本作者hi94740)
// @namespace klingeling
// @author klingeling
// @version 2.0.3.1
// @license MIT
// @description 这个脚本能帮你通过关注分组筛选b站时间线上的动态 (布局优化,排除展示番剧,避免无人直播时失效)
// @match t.bilibili.com/*
// @run-at document-idle
// @noframes
// @grant unsafeWindow
// @grant GM.getResourceUrl
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js
// @resource css https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css
// ==/UserScript==

if (document.URL == "https://t.bilibili.com/" || document.URL.startsWith("https://t.bilibili.com/?")) {

  var vmTab
  var vmBWList
  var validTagIDs
  var tagged
  var selectedUp
  var cardObserver
  var tabObserver

  const darkStyle = '<style id="btf-dark-style" type="text/css">\
                      .van-collapse{background-color:#444 !important}\
                      .van-cell__title{color:white !important}\
                      .van-switch__node{background-color:#444 !important}\
                      .van-checkbox__label{color:white !important}\
                      .van-tab{color:#aaa !important}\
                      .van-tabs__nav{background-color:#444 !important}\
                      .van-tab.van-tab--active{color:#00a1d6 !important}\
                    </style>'

  const filterDynamicWithTags = function (selections, excluded) {
    cardObserver.disconnect()
    if (selections == "shamiko") {
      clearFilters()
      autoPadding()
    } else {
      selections = _.castArray(selections).filter(t => validTagIDs.includes(t))
      excluded = _.castArray(excluded).filter(t => validTagIDs.includes(t))
      let excludedUp = excluded.map(t => (tagged[t] || { list: [] }).list).flat()
      let newSelectedUp = _.difference(_.uniq(selections.map(t => (tagged[t] || { list: [] }).list).flat()), excludedUp)
      if (newSelectedUp.length > 0) {
        selectedUp = newSelectedUp
        console.log(selections)
        new Promise(res => {
          let siid = setInterval(function () {
            if ($(".bili-dyn-item").length > 0) {
              clearInterval(siid)
              res()
            }
          })
        }).then(function () {
          clearFilters()
          filterWorker()
          cardObserver.observe($(".bili-dyn-item").parent().parent()[0], { childList: true, subtree: true })
        })
      }
    }
  }

  function filterWorker() {
    $(".bili-dyn-item").toArray().forEach(c => {
      let author = c.__vue__.author
      // console.log('【klingeling】author:' + author.label)
      if (!(selectedUp.some(up => up.mid == author.mid))) $(c)[0].hidden = true
    })
    loadMoreDynamics()
    autoPadding()
  }

  function loadMoreDynamics() {
    if ($(window).height() / ($(document).height() - $(document).scrollTop()) > 0.2) {
      $(".load-more").click()
      setTimeout(loadMoreDynamics, 100)
    } else {
      if ($(".skeleton").length > 0) {
        if (($($(".skeleton")[0]).offset().top - $(document).scrollTop()) < ($(window).height() + 1000)) {
          forceLoad()
          setTimeout(loadMoreDynamics, 100)
        }
      }
    }
  }

  function forceLoad() {
    let currentY = $(document).scrollTop()
    $(document).scrollTop($(document).height())
    $(document).scrollTop(currentY)
  }

  function clearFilters() {
    $(".bili-dyn-item").toArray().forEach(c => c.hidden = false)
  }

  function autoPadding() {
    $("#btf-tab-area").css("padding", ($(".bili-dyn-item")[0] && $(".new-notice-bar").length == 0) ? ($(".bili-dyn-item")[0].hidden ? "0px 0px 0px 0px" : "0px 0px 8px 0px") : "0px 0px 8px 0px")
  }

  function isBangumiTimeline() {
    if ($(".selected").text().includes("番") || $(".selected").text().includes("剧")) {
      $("#btf-tab-area")[0].hidden = true
      $("#btf-bwlist-area")[0].hidden = true
      cardObserver.disconnect()
      clearFilters()
    } else {
      $("#btf-tab-area")[0].hidden = false
      $("#btf-bwlist-area")[0].hidden = false
      vmTab.activeName = "shamiko"
      if (vmTab.complexMode) vmBWList.changed()
    }
  }


  const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + (i * step))

  function ajaxWithCredentials(url) {
    return new Promise((res, rej) => {
      $.ajax({
        url: url,
        xhrFields: {
          withCredentials: true
        },
        success: res,
        error: rej
      })
    })
  }

  function fetchTags(requestWithCredentials) {
    let tags = {}
    return requestWithCredentials("https://api.live.bilibili.com/User/getUserInfo")
      .then(data => {
        let uid = data.data.uid
        console.log("uid: " + uid)
        let followingsRequests = requestWithCredentials("https://api.bilibili.com/x/relation/followings?vmid=" + uid + "&pn=1&ps=50")
          .then(data => {
            let gf = range(2, Math.ceil(data.data.total / 50), 1)
              .map(i => {
                return requestWithCredentials("https://api.bilibili.com/x/relation/followings?vmid=" + uid + "&pn=" + i + "&ps=50")
              })
            gf.unshift(Promise.resolve(data))
            return gf
          })
        return requestWithCredentials("https://api.bilibili.com/x/relation/tags?vmid=" + uid)
          .then(data => {
            let tagsList = data.data
            tagsList.map(tag => {
              tag.list = []
              return tag
            }).forEach(tag => tags[tag.tagid] = tag)
            return {
              tags: tagsList,
              tagged: followingsRequests.then(gf => {
                return Promise.all(gf.map(request => {
                  return request.then(data => {
                    let followings = data.data.list
                    followings.forEach(f => {
                      if (f.tag) {
                        let noAliveTag = true
                        f.tag.forEach(t => {
                          if (tags[t]) {
                            tags[t].list.push(f)
                            noAliveTag = false
                          } else console.log("迷之tag:" + t)
                        })
                        if (noAliveTag) tags[0].list.push(f)
                      } else {
                        tags[0].list.push(f)
                      }
                    })
                  })
                })).then(() => tags)
              })
            }
          })
      })
  }



  Promise.all([
    fetchTags(ajaxWithCredentials),
    GM.getResourceUrl("css")
      .then(u => $("head").append([
        '<link rel="stylesheet" href="' + u + '">',
        '<style type="text/css">',
        '.van-collapse-item__title, .van-collapse-item__content {background-color:rgba(0,0,0,0) !important}',
        '.van-cell__value {height:24px;overflow:visible !important}',
        '.van-tab.van-tab--active{color:#00b5e5 !important}',
        '</style>'
      ].join("\n"))),
    new Promise(res => {
      cardObserver = new MutationObserver(filterWorker)
      tabObserver = new MutationObserver(isBangumiTimeline)
      Vue.use(vant.Tab)
      Vue.use(vant.Tabs)
      let siid = setInterval(function () {
        if ($(".bili-dyn-up-list").length == 1 && $(".bili-dyn-my-info").length == 1) {
          clearInterval(siid)
          res()
        }
      })
    }).then(function () {
      $(".bili-dyn-up-list").after('<div id="btf-tab-area"><div id="btf-tab"></div></div>')
      $(".bili-dyn-my-info").after('<div id="btf-bwlist-area" style="padding-top:8px"><div id="btf-bwlist"></div></div>')
      $("#btf-tab-area")[0].hidden = true
      $("#btf-bwlist-area")[0].hidden = true
      autoPadding()
      tabObserver.observe($(".bili-dyn-list-tabs")[0], { childList: true, subtree: true, attributes: true })
    })
  ]).then(data => {
    let tagOptions = data[0].tags.filter(t => t.count != 0)
    validTagIDs = tagOptions.map(t => t.tagid)
    let loadCompleted = false
    vmTab = new Vue({
      el: "#btf-tab",
      template: '<van-tabs v-model="activeName" line-height="2px" color="#00a1d6" title-inactive-color="#99a2aa" swipe-threshold="10" :border="false" @click="onClick"><van-tab v-for="tag in (complexMode ? [{tagid:\'shamiko\',name:\'已启用高级筛选\'}] : tags)" :title="tag.name" :name="tag.tagid"></van-tab></van-tabs>',
      data: {
        activeName: "shamiko",
        tags: [{ tagid: "shamiko", name: "全部" }].concat(tagOptions),
        complexMode: false
      },
      methods: {
        onClick: s => {
          if (loadCompleted) setTimeout(filterDynamicWithTags, 300, s)
          else {
            setTimeout(() => { vmTab.activeName = "shamiko" }, 100)
            vant.Toast.fail("分组名单尚未加载完成,请稍后再试!")
          }
        }
      }
    })
    vmBWList = new Vue({
      el: "#btf-bwlist",
      template: '<van-collapse v-model="nc" :border="false" style="border-radius:4px;background-color:white;" @change="switched"><van-collapse-item title="高级筛选" :border="true" :is-link="false" name="1"><template #value><van-switch :value="sw" size="22px"/></template><div style="display:flex"><van-checkbox-group v-model="blackList" checked-color="#ff2d55" style="padding-right:8px" @change="changed"><van-checkbox v-for="tag in tags" :name="tag.tagid" style="height:40px"><template #icon="{checked}"><van-icon name="cross" :color="checked?\'white\':\'#c8c9cc\'" style="line-height:19.9px" /></template></van-checkbox></van-checkbox-group><van-checkbox-group v-model="whiteList" style="flex-grow:1" @change="changed"><van-checkbox v-for="tag in tags" :name="tag.tagid" style="height:40px">{{tag.name}}<template #icon="{checked}"><van-icon name="success" :color="checked?\'white\':\'#c8c9cc\'" /></template></van-checkbox></van-checkbox-group></div></van-collapse-item></van-collapse>',
      data: {
        nc: [],
        tags: tagOptions,
        whiteList: tagOptions.map(t => t.tagid),
        blackList: []
      },
      methods: {
        switched: function (sw) {
          console.log(sw)
          if (sw.length > 0) {
            console.log("on")
            vmTab.activeName = "shamiko"
            vmTab.complexMode = true
            this.changed()
          } else {
            console.log("off")
            vmTab.complexMode = false
            filterDynamicWithTags("shamiko")
          }
        },
        changed: function () {
          setTimeout(filterDynamicWithTags, 300, this.whiteList, this.blackList)
        }
      },
      computed: {
        sw: function () {
          return this.nc.length > 0
        }
      },
      watch: {
        whiteList: function (n, o) {
          if (n.length > o.length) this.blackList = this.blackList.filter(b => b != n.filter(t => !o.includes(t)))
        },
        blackList: function (n, o) {
          if (n.length > o.length) this.whiteList = this.whiteList.filter(w => w != n.filter(t => !o.includes(t)))
        }
      }
    })
    isBangumiTimeline()
    data[0].tagged.then(data => {
      tagged = data
      loadCompleted = true
    })
    $(".van-tabs__wrap")[0].style["border-radius"] = "4px"
    try {
      if (unsafeWindow.bilibiliEvolved.settings.useDarkStyle) $("head").append(darkStyle)
      unsafeWindow.bilibiliEvolved.addSettingsListener("useDarkStyle", value => value ? $("head").append(darkStyle) : $("#btf-dark-style").remove())
    } catch (e) {
      console.log("dark mode error: ", e)
    }
  }).catch(err => {
    console.error(err)
    alert("【b站时间线筛选】脚本出错了!\n请查看控制台以获取错误信息")
  })

}