bilibili - display video av, bv number below the title bar

Display the video av, bv, cid number below the title bar of the bilibili play page, support copy short link

目前為 2020-06-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name                bilibili - display video av, bv number below the title bar
// @name:zh-CN          b 站 - 显示视频 av 号、bv 号及弹幕 cid
// @namespace           https://gist.github.com/phtwo
// @version             0.3
// @licence             MIT. Copyright (c) phtwo.
// @description         Display the video av, bv, cid number below the title bar of the bilibili play page, support copy short link
// @description:zh-CN   在 b 站播放页标题栏下面显示视频 av 号、bv 号及弹幕 cid,支持复制短链接
// @match               *://www.bilibili.com/video/*
// @grant               GM_addStyle
//
// @author              phtwo
// @homepage            https://gist.github.com/phtwo/e2d8c04707ed6a6369a55be37e3f14c7
// @supportURL          https://gist.github.com/phtwo/e2d8c04707ed6a6369a55be37e3f14c7
//
// @noframes
// @nocompat Chrome
//
// ==/UserScript==

/**
 * @licence MIT. Copyright (c) Feross Aboukhadijeh.
 * @description From https://github.com/feross/clipboard-copy
 * @param text
 * @returns {Promise<void>|any}
 */
function clipboardCopy(text) {
  // Use the Async Clipboard API when available. Requires a secure browsing
  // context (i.e. HTTPS)
  if (navigator.clipboard) {
    return navigator.clipboard.writeText(text).catch(function (err) {
      throw (err !== undefined ? err : new DOMException('The request is not allowed', 'NotAllowedError'))
    })
  }

  // ...Otherwise, use document.execCommand() fallback

  // Put the text to copy into a <span>
  const span = document.createElement('span')
  span.textContent = text

  // Preserve consecutive spaces and newlines
  span.style.whiteSpace = 'pre'

  // Add the <span> to the page
  document.body.appendChild(span)

  // Make a selection object representing the range of text selected by the user
  const selection = window.getSelection()
  const range = window.document.createRange()
  selection.removeAllRanges()
  range.selectNode(span)
  selection.addRange(range)

  // Copy text to the clipboard
  let success = false
  try {
    success = window.document.execCommand('copy')
  } catch (err) {
    console.log('error', err)
  }

  // Cleanup
  selection.removeAllRanges()
  window.document.body.removeChild(span)

  return success
    ? Promise.resolve()
    : Promise.reject(new DOMException('The request is not allowed', 'NotAllowedError'))
}

(function () {

  const classPref = 'p-bevd'

  const styleCssText = `
.${classPref}-data-item {
  position: relative!important;
  color: #afafaf!important;
}

.${classPref}-data-item:hover .${classPref}-hover-box {
  display: inline-block!important;
}

.${classPref}-hover-box {
  display: none!important;
  vertical-align: top!important;
  
  position: absolute!important;
  bottom: 1.2em;
  right: 0;
  padding: 0 0 0 10px!important;
}

.${classPref}-copy-link {
  color: #afafaf!important;
}

.${classPref}-copy-link:hover {
  color: #00a1d6!important;
}
  `

  const urlNodeText = '短链接'

  const list = []

  /** @type Element */
  let videoDataLineElem

  init()

  function init() {
    console.log('bilibili_extra_video_data init')

    initVideoDataLineElem()
    if (!videoDataLineElem) {
      return
    }

    util_addStyle(styleCssText)

    initMonitor()
  }

  function initVideoDataLineElem() {
    videoDataLineElem = document.querySelector('#viewbox_report .video-data:nth-of-type(2)')
  }

  function initMonitor() {
    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        if ('attributes' === mutation.type) {
          observer.takeRecords()
          run()
          break
        }
      }
    })

    observer.observe(videoDataLineElem.children[0], {
      attributes: true,
      attributeFilter: ['title'],
    })
  }

  function run() {
    const {aid, bvid, cid} = unsafeWindow

    list.length = 0 // clear list
    addItem('av', aid, true)
    addItem('', bvid, true)
    addItem('cid', cid)

    const fragment = createFragment()

    const referenceNode = videoDataLineElem.querySelector('.copyright')
    removeOldNodes()
    videoDataLineElem.insertBefore(fragment, referenceNode)

    console.log('bilibili_extra_video_data run finished')
  }

  function createFragment() {
    const firstPrefix = '&nbsp;&nbsp;'
    const prefix = '&nbsp;·&nbsp;'

    const nodeList = list.map((item, index) => {
      let text = item.prefix + item.value
      let innerHTML = (index === 0 ? firstPrefix : prefix) + text

      const elem = document.createElement('span')
      elem.title = text
      elem.innerHTML = innerHTML
      elem.classList.add(`${classPref}-data-item`)
      elem.setAttribute('data-inject', '')

      item.hasShortLink && elem.append(createHoverElem(text))

      return elem
    })

    const fragment = document.createDocumentFragment()
    fragment.append(...nodeList)
    return fragment
  }

  function createHoverElem(id) {

    const aNode = document.createElement('a')
    aNode.classList.add(`${classPref}-copy-link`)
    aNode.href = 'https://b23.tv/' + id
    aNode.textContent = urlNodeText
    aNode.title = '单击复制,右键打开菜单'

    aNode.addEventListener('click', copyUrlOnClick)

    const e = document.createElement('span')
    e.classList.add(`${classPref}-hover-box`)
    e.append(aNode)
    return e
  }

  function copyUrlOnClick(ev) {
    ev.preventDefault()
    ev.stopPropagation()

    const target = ev.target
    const text = target.href || ''

    clipboardCopy(text)
      .then(async () => {
        console.log('bilibili_extra_video_data copy [%s] to clipboard succeeded.', text)
        target.textContent = '已复制!'
        await util_delay(1e3)
        target.textContent = urlNodeText
      }, () => {
        console.error('bilibili_extra_video_data copy [%s] to clipboard failed.', text)
        window.prompt('自动复制失败,请手动复制', text)
      })
  }

  function removeOldNodes() {
    videoDataLineElem
      .querySelectorAll('[data-inject]')
      .forEach(node => node.parentNode.removeChild(node))
  }

  function addItem(prefix = '', value, hasShortLink = false) {
    value && list.push({prefix, value, hasShortLink})
  }

  function util_addStyle(css) {
    if (typeof GM_addStyle !== 'undefined') {
      return GM_addStyle(css)
    }

    const styleNode = document.createElement("style")
    styleNode.appendChild(document.createTextNode(css))
    ;(document.querySelector("head") || document.documentElement).appendChild(styleNode)
    return styleNode
  }

  function util_delay(timeout = 300, doReject) {
    return new Promise((resolve, reject) => {
      setTimeout(doReject ? reject : resolve, timeout)
    })
  }

})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址