Greasy Fork is available in English.

Bilibili Mobile Lite

view bilibili mobile page recommended video directly

// ==UserScript==
// @name               Bilibili Mobile Lite
// @name:zh-CN         bilibili 移动端 Lite
// @namespace          https://github.com/jk278/bilibili-mobile
// @version            2.9.2
// @description        view bilibili mobile page recommended video directly
// @description:zh-CN  b 站移动端网页推荐视频直接看
// @author             jk278
// @match              https://m.bilibili.com/*
// @grant              none
// @run-at             document-start
// @icon               https://www.bilibili.com/favicon.ico
// ==/UserScript==

(function () {
  'use strict'
  console.log('Bilibili mobile execute!')

  removeAdButton()

  const pathname = window.location.pathname

  if (pathname.startsWith('/video')) {
    customElementStyle()
  }

  waitDOMContentLoaded(() => {
    if (pathname.startsWith('/video')) {
      autoplay()
      preventAutoCallApp()
      removeFullscreenAd()
    } else if (pathname === '/' || pathname === '') {
      runHome()
    }
  })

  // DOM 加载完后
  function waitDOMContentLoaded(callback) {
    document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', callback) : callback()
  }

  // 阻止点击视频区跳转APP
  function preventAutoCallApp() {
    // 阻止点击视频区跳转APP
    const autoCallApp = document.querySelector('.mplayer-display-call-app')
    autoCallApp.addEventListener('click', (event) => {
      event.preventDefault()
      event.stopImmediatePropagation()
    })

    const playButton = document.querySelector('.mplayer-pause-call-app')
    playButton.addEventListener('click', (event) => {
      event.preventDefault()
      event.stopImmediatePropagation()
      // 播放监听此时还未被阻止
    })
  }

  // 全屏后,全屏广告元素样式变为 flex !important
  function removeFullscreenAd() {
    // 全屏后跳App的横幅、清晰度按钮、弹幕提示
    document.addEventListener('fullscreenchange', function () {
      const adBanner = document.querySelector('.mplayer-widescreen-callapp')
      const qualityBtn = document.querySelector('.mplayer-control-btn-quality')
      const text = document.querySelector('.mplayer-comment-text')

      adBanner.style.cssText = 'display: none !important;'
      qualityBtn.style.cssText = 'display: none !important;'
      text.style.cssText = 'display: none !important;'

      const commentContent = document.querySelector('.mplayer-btn-comment-content')
      commentContent.style.cssText = 'position: absolute; left: 11px; width: 24px !important;'
    })
  }

  function removeAdButton() {
    const ad1 = '.home-float-openapp, .open-app, .m-nav-openapp, .m-float-openapp, [class^="m-video2-awaken-btn"]'
    // 两个底部出现的弹窗和一个点击播放后的弹窗
    const ad2 = '.openapp-dialog, .caution-dialog, .v-dialog'
    const style = document.createElement('style')
    style.textContent = `${ad1}, ${ad2}{ display: none !important; }`

    // 如果 document.head 可用,将样式添加到文档
    document.head ? document.head.appendChild(style) : waitDOMContentLoaded(document.head.appendChild(style))
  }

  function goToVideoById(keyword, callback) {
    const callbackName = `jsonp_callback_${Date.now()}_${Math.floor(Math.random() * 100000)}`

    window[callbackName] = function (responseData) {
      if (responseData.data.result[11].data[0]) {
        const bvId = responseData.data.result[11].data[0].bvid
        callback(bvId, null)
      } else {
        callback(null, 'BVId not found')
      }
      delete window[callbackName]
    }

    const script = document.createElement('script')
    script.src = `https://api.bilibili.com/x/web-interface/search/all/v2?page=1&keyword=${keyword}&jsonp=jsonp&callback=${callbackName}`
    document.body.appendChild(script)
  }

  // 自动播放
  function autoplay() {
    const play = document.querySelector('.main-cover')

    const style = document.createElement('style')
    style.textContent = '.m-navbar + div { display: block !important }'
    document.head.appendChild(style)

    if (play) {
      const video = document.querySelector('video')
      if (video) {
        video.playsInline = true
        // 先添加播放监听,第一次播放时执行
        video.addEventListener('play', function () {
          if (video.muted === true) createUnmuteButton()
        }, { once: true })

        const startPlayPromise = video.play()

        if (startPlayPromise !== undefined) {
          startPlayPromise
            .catch((error) => {
              if (error.name === 'NotAllowedError') {
                video.muted = true
                video.play()
              } else {
                // 处理加载或播放错误
              }
            })
            .then(() => {
              // 仅在播放开始后才开始执行你需要执行的操作。
            })
        }

        // 创建解除静音按钮
        function createUnmuteButton() {
          // 检查是否已存在解除静音按钮
          if (document.getElementById('unmuteButton')) {
            return // 如果已存在,不进行重复创建
          }
          // 创建按钮元素
          const button = document.createElement('button')
          button.classList.add('unmute')
          button.innerHTML = `
<div class="unmute-inner">
    <div class="unmute-icon"><svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
            <use class="svg-shadow" xlink:href="#ytp-id-1"></use>
            <path class="ytp-svg-fill"
                d="m 21.48,17.98 c 0,-1.77 -1.02,-3.29 -2.5,-4.03 v 2.21 l 2.45,2.45 c .03,-0.2 .05,-0.41 .05,-0.63 z m 2.5,0 c 0,.94 -0.2,1.82 -0.54,2.64 l 1.51,1.51 c .66,-1.24 1.03,-2.65 1.03,-4.15 0,-4.28 -2.99,-7.86 -7,-8.76 v 2.05 c 2.89,.86 5,3.54 5,6.71 z M 9.25,8.98 l -1.27,1.26 4.72,4.73 H 7.98 v 6 H 11.98 l 5,5 v -6.73 l 4.25,4.25 c -0.67,.52 -1.42,.93 -2.25,1.18 v 2.06 c 1.38,-0.31 2.63,-0.95 3.69,-1.81 l 2.04,2.05 1.27,-1.27 -9,-9 -7.72,-7.72 z m 7.72,.99 -2.09,2.08 2.09,2.09 V 9.98 z"
                id="id-1"></path>
        </svg></div>
    <div class="unmute-text">点按取消静音</div>
    <div class="unmute-box"></div>
</div>
          `
          // 按钮点击事件
          button.addEventListener('click', function () {
            video.muted = false
            button.remove()
          })

          // 将按钮添加到页面的合适位置
          const videoWrapper = document.querySelector('.mplayer-video-wrap')
          videoWrapper.insertAdjacentElement('afterend', button)
          // 第一个参数是函数,而非调用函数的结果
          setTimeout(() => {
            button.classList.add('animated')
          }, 4500)
        }
      }
    }

    observeCardBox()
  }

  // 观察推荐视频的加载
  function observeCardBox() {
    const cardBox = document.querySelector('.card-box')
    const targetElements = cardBox.children

    // 为初始子元素添加监听器
    Array.from(targetElements).forEach(addTargetElementListener)

    // 创建 MutationObserver 以监听子元素的变化
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((addedNode) => {
            addTargetElementListener(addedNode)
          })
        }
      })
    })

    // 配置观察选项
    const observerConfig = {
      childList: true
    }

    // 开始观察
    observer.observe(cardBox, observerConfig)
  }

  function addTargetElementListener(targetElement) {
    if (targetElement) {
      const anchor = targetElement.firstChild
      const h2Element = anchor.lastChild
      const keyword = encodeURIComponent(h2Element.innerHTML)

      anchor.addEventListener('click', event => {
        event.preventDefault()
        event.stopImmediatePropagation()

        goToVideoById(keyword, (bvId, error) => {
          if (bvId) {
            const videoUrl = `https://m.bilibili.com/video/${bvId}`
            window.history.pushState({}, '', videoUrl)
            window.location.href = videoUrl
          } else {
            console.error('BVId wrong: ', error)
          }
        })
      }, true)
    }
  }

  function runHome() {
    const cardBox = document.querySelector('.card-box')
    const aTags = cardBox.children

    Array.from(aTags).forEach(addHomeTargetElementListener)

    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((addedNode) => {
            addHomeTargetElementListener(addedNode)
          })
        }
      })
    })

    const observerConfig = {
      childList: true
    }

    observer.observe(cardBox, observerConfig)

    function addHomeTargetElementListener(tag) {
      tag.addEventListener('click', async (event) => { // 异步,然后放到 js 末尾执行
        event.preventDefault()
        event.stopImmediatePropagation() // 阻止其他事件监听器的执行

        // 等待下一个事件循环迭代
        await new Promise((resolve) => setTimeout(resolve, 0)) // THIS!
        window.location.href = tag.getAttribute('href')
      }, true)
    }
  }

  function customElementStyle() {
    const initialInsertStyle = `
/* 全屏跳App、倍速按钮、播完推荐 */
.mplayer-fullscreen-call-app, .mplayer-control-btn-speed, .mplayer-ending-panel-recommend {
  display: none !important;
}

/*
* 优化视觉 *
*/

/* 调整分集高度 */
.m-video-part-panel-content {
  height: 81vmin !important;
}
/* 居中重播按钮 */
.mplayer-ending-panel-buttons {
  align-self: center !important;

  img {
    margin-left: 3px !important;
  }
}
/* 阻止跳转APP */
.launch-app-btn {
  pointer-events: none;
}
.card-box a {
  pointer-events: auto;
}
/* 重复的初始图形层 */
.natural-module, .m-footer {
  display: none !important;
}

/*
* 声音按钮 *
*/

.unmute {
  position: absolute;
  top: 0;
  padding: 12px;
  background: none;
  border: 0;
  font-size: 127%;
  text-align: inherit;
}
.unmute-inner {
  position: relative;
}
.unmute-icon {
  height: 48px;
  display: inline-block;
  vertical-align: middle;
  padding-left: 2px;
  position: relative;
  z-index: 10;
  background-color: rgb(255, 255, 255);
  border-radius: 2px;
  border-bottom: 1px solid #f1f1f1;
}
.unmute svg {
  filter: drop-shadow(0 0 2px rgba(0,0,0,.5));
}
.unmute-text {
  position: relative;
  z-index: 10;
  padding-right: 10px;
  vertical-align: middle;
  display: inline-block;
  transition: opacity .25s cubic-bezier(.4,0,1,1);
}
.animated .unmute-text {
  opacity: 0;
}
.unmute-box {
  width: 100%;
  background-color: rgb(255, 255, 255);
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 2px;
  border-bottom: 1px solid #f1f1f1;
  transition: width .5s cubic-bezier(.4,0,1,1);
}
.animated .unmute-box {
  width: 0;
}
    `

    const style = document.createElement('style')
    style.textContent = initialInsertStyle

    // 如果 document.head 可用,将样式添加到文档
    document.head ? document.head.appendChild(style) : waitDOMContentLoaded(document.head.appendChild(style))
  }
})()