🏠 Home 

Add URL to the recommend interface when Bilibili video player finishes playing

Modify the recommended video URL of the Bilibili video player to support the middle mouse button clicks


Install this script?
Author's suggested script

You may also like bilibili - display video av, bv number below the title bar.


Install this script
  1. // ==UserScript==
  2. // @name Add URL to the recommend interface when Bilibili video player finishes playing
  3. // @name:zh-CN 给 Bilibili 视频播放器结束播放后的推荐视频界面添加网址
  4. // @namespace https://gist.github.com/phtwo
  5. // @version 0.2.1
  6. // @description Modify the recommended video URL of the Bilibili video player to support the middle mouse button clicks
  7. // @description:zh-CN 给 Bilibili 视频播放器结束播放后的推荐视频界面添加网址,以支持新标签页打开
  8. // @match *://www.bilibili.com/video/*
  9. // @grant none
  10. //
  11. // @author phtwo
  12. // @homepage https://gist.github.com/phtwo/b7bee4787e3dcce1bda7c17535538097
  13. // @supportURL https://gist.github.com/phtwo/b7bee4787e3dcce1bda7c17535538097
  14. //
  15. // @noframes
  16. // @nocompat Chrome
  17. //
  18. // ==/UserScript==
  19. (function () {
  20. 'use strict'
  21. init()
  22. function init() {
  23. startMonitor()
  24. observeVideoChange(videoPageUrl => {
  25. // 每次切换视频后,直接重新再执行一次。嗯,就这样。
  26. setTimeout(startMonitor, 2e3)
  27. console.log('Bilibili recommended video URL modifier observeVideoChange', videoPageUrl)
  28. })
  29. }
  30. function startMonitor() {
  31. let waitVideosNodes = () => {
  32. return isRecommendVideosNodesExist() ? Promise.resolve() : Promise.reject()
  33. }
  34. waitingForChildElement('.bilibili-player-video-wrap', 'bilibili-player-ending-panel') // 首次进入首页 等待 播放器结束面板 渲染
  35. .then(waitVideosNodes)
  36. .catch(() => waitingForChildElement('.bilibili-player-ending-panel-box-videos', 'bilibili-player-ending-panel-box-recommend')) // 播放器结束面板渲染后,等待推荐视频模块渲染
  37. .then(modifyRecommendVideosNodesLink)
  38. .catch(error => console.error('Bilibili recommended video URL modifier error', error))
  39. console.log('Bilibili recommended video URL modifier is waiting to be modified.')
  40. }
  41. function isRecommendVideosNodesExist() {
  42. return getRecommendVideosNodes().length > 0
  43. }
  44. function getRecommendVideosNodes() {
  45. return document.querySelectorAll('a.bilibili-player-ending-panel-box-recommend')
  46. }
  47. function modifyRecommendVideosNodesLink() {
  48. getRecommendVideosNodes().forEach(item => {
  49. const aid = item.getAttribute('data-aid')
  50. const bvId = item.getAttribute('data-bvid')
  51. const videoId = aid ?
  52. `av${aid}` :
  53. bvId ? `BV${bvId}` : ''
  54. videoId && item.setAttribute('href', '//www.bilibili.com/video/' + videoId)
  55. })
  56. console.log('Bilibili recommended video URL modifier has been modified.')
  57. }
  58. /**
  59. * @name waitingForChildElement
  60. * @description 使用 MutationObserver 接口观察 父 element, childList addedNodes 中的直接子代,有任意一个具有 childClass 类名即为完成
  61. * @param {string} parentSlector - 父 element 选择器
  62. * @param {string} childClass - 直接子代类名,不支持选择器语法
  63. * @return {Promise}
  64. */
  65. function waitingForChildElement(parentSlector, childClass) {
  66. const deferred = createPromiseDeferred()
  67. const parentDom = document.querySelector(parentSlector)
  68. const options = {
  69. childList: true,
  70. }
  71. const observer = new MutationObserver(mutationCallback)
  72. observer.observe(parentDom, options)
  73. return deferred.promise
  74. function mutationCallback(mutations) {
  75. for (let mutation of mutations) {
  76. if ('childList' !== mutation.type) {
  77. continue
  78. }
  79. if (Array.from(mutation.addedNodes).some(node => 1 === node.nodeType &&
  80. node.classList.contains(childClass))) {
  81. observer.takeRecords()
  82. observer.disconnect()
  83. deferred.resolve()
  84. break
  85. }
  86. }
  87. }
  88. }
  89. /**
  90. * @name observeVideoChange
  91. * @description 因为每次切视频,都会销毁旧的播放器实例。 因此切换视频后,必须重新对新生成的 DOM 创建 MutationObserver。
  92. * 这里采用监听 'head> meta[itemprop=url]' 的 content 变化来跟踪页面的切换
  93. * ps: 这里可对 head 的检测进行节流处理,回调里直接读取 meta 更快,b 站都是先改 url 和 meta url 的值
  94. * @param {function} [fCallback]
  95. * @return {Promise}
  96. */
  97. function observeVideoChange(fCallback) {
  98. let lastVideo = getVideoPageUrlFromMetaTag()
  99. const parentSlector = 'head'
  100. const parentDom = document.querySelector(parentSlector)
  101. const options = {
  102. childList: true,
  103. }
  104. const observer = new MutationObserver(mutationCallback)
  105. observer.observe(parentDom, options)
  106. function mutationCallback(mutations) {
  107. for (let mutation of mutations) {
  108. if ('childList' !== mutation.type) {
  109. continue
  110. }
  111. let urlMetaTag = Array.from(mutation.addedNodes).find(node => {
  112. return 1 === node.nodeType && 'meta' === node.tagName.toLowerCase() &&
  113. 'url' === node.getAttribute('itemprop')
  114. })
  115. if (!urlMetaTag) {
  116. continue
  117. }
  118. let currVideoPage = getVideoPageUrlFromMetaTag(urlMetaTag)
  119. if (currVideoPage === lastVideo) {
  120. continue
  121. }
  122. lastVideo = currVideoPage
  123. observer.takeRecords() // 已经确定当前有切换视频了,忽略其他变动
  124. fCallback(lastVideo)
  125. break
  126. }
  127. }
  128. }
  129. /**
  130. * @name getVideoPageUrlFromMetaTag
  131. * @description 无需取 avid ,这个 url 的格式不包含其他参数的,仅仅只有 avid
  132. * @param {HTMLMetaElement=} metaTag
  133. * @return {string}
  134. */
  135. function getVideoPageUrlFromMetaTag(metaTag) {
  136. let meta = metaTag || document.querySelector('meta[itemprop=url]')
  137. return (meta && meta.getAttribute('content')) || ''
  138. }
  139. function createPromiseDeferred() {
  140. let resolve, reject
  141. let promise = new Promise((res, rej) => {
  142. resolve = res
  143. reject = rej
  144. })
  145. return {
  146. promise,
  147. resolve,
  148. reject
  149. }
  150. }
  151. })()