B站动态自定义过滤

B站动态自定义过滤,可过滤转发类型(关注分组已无法使用)

// ==UserScript==
// @name         B站动态自定义过滤
// @namespace    http://tampermonkey.net/
// @version      0.1.3
// @description  B站动态自定义过滤,可过滤转发类型(关注分组已无法使用)
// @author       Eric Lam
// @include      /^https?:\/\/t\.bilibili\.com\/[^\/]*$/
// @require      https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(async function() {
    'use strict';
    const posts = {
        normal: [],
        videoRel: [],
        repost: [],
        videos: [],
        followings: {}
    }
    const defaultEnabled = {
        normal: true,
        videoRel: true,
        repost: true,
        videos: true
    }
    const followings = {}
    function getSettings(){
       try {
         return { ...defaultEnabled, ...JSON.parse(window.localStorage['hide_feed_settings']) }
       }catch(err){
         console.debug(`cannot found old settings: [${err.message}], using default settings`)
         return defaultEnabled
       }
    }
    const enabled = getSettings()
    for (const key in enabled){
       if (!Object.keys(defaultEnabled).includes(key)) delete enabled[key]
    }

    let allFeedsState = true

    const feedCardCallback = (mu, o) => {
       for (const nodes of mu){
          if ($(nodes.target).hasClass('loading-content') && $(nodes.addedNodes[0]).hasClass('tc-slate')){
             console.debug('changed tab')
             posts.repost = []
             posts.normal = []
             posts.videos = []
             posts.videoRel = []
             return
          }
          for (const node of nodes.addedNodes){
             const tid = parseInt(getTagId())
             if ($(node).find('.bili-dyn-content__orig.reference').length > 0) {
                console.debug('found repost')
                const repostContent = $(node).find('.bili-dyn-content__orig.reference')
                const isVideo = repostContent.find('.bili-dyn-card-video').length > 0
                console.debug(`this repost is video: ${isVideo}`)
                const card = repostContent.parents('.bili-dyn-item')
                if (isVideo){
                  posts.videos.push(card)
                  if (!enabled.videos) $(card).hide()
                }else{
                  posts.repost.push(card)
                  if (!enabled.repost) $(card).hide()
                }
             } else if($(node).find('.bili-dyn-content__orig').length > 0){
                console.debug('found normal')
                const content = $(node).find('.bili-dyn-content__orig')
                const card = content.parents('.bili-dyn-item')
                const isVideo = content.find('.bili-dyn-card-video').length > 0
                console.debug(`this normal is video: ${isVideo}`)
                if (isVideo){
                  posts.videoRel.push(card)
                  if (!enabled.videoRel) $(card).hide()
                }else{
                  posts.normal.push(card)
                  if (!enabled.normal) $(card).hide()
                }
             } else {
               console.debug('found unknown post')
             }
             //if (allFeedsState) handleGroupFilter(node).catch(console.error)
          }
       }
    }

    let feedCard = $('.bili-dyn-list')

    while(feedCard.length == 0){
       console.log('bili-dyn-list not found, wait 0.5 sec')
       await sleep(500)
       feedCard = $('.bili-dyn-list')
    }

    try {
       new MutationObserver(feedCardCallback).observe(feedCard[0], { subtree: true, childList: true, attributes: false })
    }catch(err){
      alert(`自定义过滤载入失败,${err.message}, 请刷新`)
      return
    }

    const hideAll = (arr) => arr.forEach(s => $(s).hide())
    const showAll = (arr) => arr.forEach(s => $(s).show())

    function handle(key, target){
        const val = $(target).prop('checked')
        enabled[key] = val
        // 关注分组失效
        //const tid = parseInt(getTagId())
        //const cards = posts[key].filter(c => tid == 0 || jqInclude(posts.followings[tid],c))
        const cards = posts[key]
        if (val){
            showAll(cards)
        }else{
            hideAll(cards)
        }
    }

    function jqInclude(arr, c){
      return arr.includes(c) || arr.some(r => r[0] == c[0])
    }

    feedCard.parent('section').before(`
         <div class="tab-bar filter-list filter-grid">
            <div>
              <input id="normal-checker" type="checkbox" checked>纯动态
            </div>
            <div>
              <input id="video-release-checker" type="checkbox" checked>投稿视频
            </div>
            <div>
              <input id="repost-checker" type="checkbox" checked>转发动态
            </div>
            <div>
              <input id="repost-video-checker" type="checkbox" checked>转发视频
            </div>
         </div>
         <!--div class="tab-bar filter-list" id="followings-group">
            分组过滤:
            <select id="f-groups" class="filter-select" value="0">
            </select>
         </div-->
         <style>
            .filter-list {
              background-color: white;
              min-height: 10px;
              margin-bottom: 10px;
              text-align: center;
              padding: 15px;
            }
            .filter-grid{
              display: grid;
              grid-template-columns: repeat(4, 3fr);
            }
            .filter-select {
              position: relative;
              padding: 5px;
              flex-direction: column;
              width: 50%;
              border-style: solid;
              border-radius: 3px;
              border-width: 1px;
              border-color: #c9c9c9;
            }
         </style>
    `)

    const followGroupInfo = {}


    /*
    const groups = await getFollowingGroups()
    for (const group of groups){
        const key = group.tagid
        if (group.tagid === 0) group.name = '全部关注'
        $('#f-groups').append(`
           <option value="${group.tagid}">${group.name}</option>
        `)
        followGroupInfo[group.tagid] = group
        posts.followings[group.tagid] = []
    }
    */


    //https://api.bilibili.com/x/relation/whispers?pn=1&ps=20&jsonp=jsonp

    $('#f-groups').val(0)

    $('input#normal-checker').prop('checked', enabled.normal)
    $('input#video-release-checker').prop('checked', enabled.videoRel)
    $('input#repost-checker').prop('checked', enabled.repost)
    $('input#repost-video-checker').prop('checked', enabled.videos)

    $('input#normal-checker').on('change', e => handle('normal', e.target))
    $('input#video-release-checker').on('change', e => handle('videoRel', e.target))
    $('input#repost-checker').on('change', e => handle('repost', e.target))
    $('input#repost-video-checker').on('change', e => handle('videos', e.target))

    /*
    $('#f-groups').on('change', e => {
       const tagid = parseInt(e.target.value)
       if (tagid === 0) {
         showAll(Object.values(posts.followings).flatMap(n => n))
       }else{
         let filter = (c) => true
         for(const tid in posts.followings){
             const cards = posts.followings[tid]
             if (tid == tagid){
                 showAll(cards)
                 filter = (c) => !cards.includes(c)
             }else{
                 hideAll(cards.filter(filter))
             }
         }
       }
       for(const key in enabled){
          const val = enabled[key]
          const tid = parseInt(getTagId())
          const cards = posts[key].filter(c => tid == 0 || jqInclude(posts.followings[tid],c))
          if (!val){
            hideAll(cards)
          }
       }
    })
    */

    window.onunload = function(){
       window.localStorage['hide_feed_settings'] = JSON.stringify(enabled)
    }

    /*
    while($('.bili-dyn-up-list__content').length == 0){
       console.log('bili-dyn-up-list__content not found, wait 0.5 sec')
       await sleep(500)
    }

    try{
      new MutationObserver(([mu], o) => {
        console.log($(mu.target).find('bili-dyn-up-list__item.active > .bili-dyn-up-list__item__name')[0])
        const allTargets = $(mu.target).hasClass('.active') && $(mu.target).find('.bili-dyn-up-list__item__name')[0]?.innerText == '全部动态'
        allFeedsState = allTargets
        if (allTargets){
           console.debug('showing followings group')
           $('#followings-group').show()
        }else{
            console.debug('hiding followings group')
           $('#followings-group').hide()
           $('#f-groups').val(0)
           for(const tid in posts.followings){
             posts.followings[tid] = []
           }
        }
      }).observe($('.bili-dyn-up-list__content')[0], { childList: false, subtree: true, attributes: true})
    }catch(err){
       alert(`自定义过滤载入失败,${err.message}, 请刷新`)
       return
    }


    async function handleGroupFilter(node){
      if (!$(node).hasClass('main-content')) return
      try {
        const reg = /(\d+)\/dynamic$/
        const card = $(node).parents('.bili-dyn-item')
        const url = card.find('a.c-pointer.user-head').prop('href')
        const regexResult = reg.exec(card.find('a.c-pointer.user-head').prop('href'))
        if (!regexResult){
           // 无效uid,可能是番剧?
           return
        }
        const mid = parseInt(regexResult.pop())
        if (!followings[mid]) followings[mid] = await getUserGroups(mid)
        if (followings[mid].length == 0){
           posts.followings[0].push(card)
           $(node).find('.bili-dyn-time')[0].innerText += ' (默认分组)'
        }else{
           for (const tagid of followings[mid]){
               posts.followings[tagid].push(card)
               $(node).find('.bili-dyn-time')[0].innerText += ` (${followGroupInfo[tagid].name})`
           }
        }
        const currentSelect = getTagId()
        if (!followings[mid].includes(currentSelect) && currentSelect != 0) card.hide()
      }catch(err){
        console.error(err)
      }
    }
    */

})().catch(console.error);

async function sleep(ms) {
   return new Promise((res,) => setTimeout(res, ms))
}

function getTagId(){
  return $('#f-groups').val()
}

async function getFollowingGroups(){
  try {
    const { data } = await webRequest('https://api.bilibili.com/x/relation/tags?jsonp=jsonp')
    return data
  }catch(err){
    console.error(err)
    return []
  }
}

async function getUserGroups(uid){
  try {
    const { data } = await webRequest(`https://api.bilibili.com/x/relation/tag/user?fid=${uid}&jsonp=jsonp`)
    return Object.keys(data)
  }catch(err){
    console.error(err)
    return []
  }
}

async function webRequest(url){
  const res = await fetch(url, { credentials: 'include' })
  if (!res.ok) {
     let backupResponse = undefined
     if (res.status == 412){
       console.warn(`412 request too fast`)
       res.statusText = '请求过快导致被B站服务器禁止'
       backupResponse = GM_getValue(`cache:${url}`, undefined)
     }
     if (backupResponse) {
       console.warn(`using backup http cache:`)
       console.log(backupResponse)
       return backupResponse
     }
     else throw { ...res, message: `${res.statusText}(${res.status})` }
  }
  const json = await res.json()
  if (json.code) throw json
  GM_setValue(`cache:${url}`, json)
  return json
}

async function* getFollowings(tagId, uid){
  let page = 1
  while(true){
      const { data }  = await webRequest(`https://api.bilibili.com/x/relation/tag?mid=${uid}&tagid=${tagId}&pn=${page++}&ps=50&jsonp=jsonp`)
      if (data.length === 0) break;
      yield data.map(r => r.mid)
  }
}