Greasy Fork is available in English.

bilibili时间线筛选——分组查看b站动态

这个脚本能帮你通过关注分组筛选b站时间线上的动态

// ==UserScript==
// @name bilibili时间线筛选——分组查看b站动态
// @namespace hi94740
// @author hi94740
// @version 2.0.0-1
// @license MIT
// @description 这个脚本能帮你通过关注分组筛选b站时间线上的动态
// @include https://t.bilibili.com/*
// @run-at document-idle
// @noframes
// @grant unsafeWindow
// @grant GM.getResourceUrl
// @require https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
// @require https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js
// @require https://cdn.jsdelivr.net/npm/vant@2.8/lib/vant.min.js
// @resource css https://cdn.jsdelivr.net/npm/vant@2.8/lib/index.css
// ==/UserScript==

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

var $ = unsafeWindow.jQuery

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 ($(".card").length > 0) {
            clearInterval(siid)
            res()
          }
        })
      }).then(function() {
        clearFilters()
        filterWorker()
        cardObserver.observe($(".card").parent()[0],{childList:true,subtree:true})
      })
    }
  }
}

function filterWorker() {
  $(".card").toArray().forEach(c => {
    let href = $(c).children()[0].href
    if (href) {
      if (href.startsWith("https://space.bilibili.com/")) {
        if (!(selectedUp.some(up => up.mid == href.replace("https://space.bilibili.com/","").replace("/dynamic","")))) $(c)[0].hidden = true
      }
      if (href.startsWith("https://bangumi.bilibili.com/")) $(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() {
  $(".card").toArray().forEach(c => c.hidden = false)
}

function autoPadding() {
  $("#btf-tab-area").css("padding",($(".card")[0] && $(".new-notice-bar").length == 0) ? ($(".card")[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 ($(".tab-bar").length == 1 && $(".user-wrapper").length == 1) {
        clearInterval(siid)
        res()
      }
    })
  }).then(function() {
    $(".tab-bar").after('<div id="btf-tab-area"><div id="btf-tab"></div></div>')
    $(".user-wrapper").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($(".tab-bar")[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请查看控制台以获取错误信息")
})

}