bilibili 移动端 Lite

b 站移动端网页推荐视频直接看

目前为 2024-03-28 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Bilibili Mobile Lite
  3. // @name:zh-CN bilibili 移动端 Lite
  4. // @namespace https://github.com/jk278/bilibili-mobile
  5. // @version 2.8.2
  6. // @description view bilibili mobile page recommended video directly
  7. // @description:zh-CN b 站移动端网页推荐视频直接看
  8. // @author jk278
  9. // @match *://m.bilibili.com
  10. // @match *://m.bilibili.com/video/*
  11. // @grant none
  12. // @run-at document-start
  13. // @icon https://www.bilibili.com/favicon.ico
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict'
  18. console.log('Bilibili mobile execute!')
  19.  
  20. removeAdButton()
  21.  
  22. const pathname = window.location.pathname
  23.  
  24. if (pathname.startsWith('/video')) {
  25. customElementStyle()
  26. }
  27.  
  28. waitDOMContentLoaded(() => {
  29. if (pathname.startsWith('/video')) {
  30. autoplay()
  31. preventAutoCallApp()
  32. removeFullscreenAd()
  33. } else if (pathname === '/' || pathname === '') {
  34. runHome()
  35. }
  36. })
  37.  
  38. // DOM 加载完后
  39. function waitDOMContentLoaded (callback) {
  40. document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', callback) : callback()
  41. }
  42.  
  43. // 阻止点击视频区跳转APP
  44. function preventAutoCallApp () {
  45. // 阻止点击视频区跳转APP
  46. const autoCallApp = document.querySelector('.mplayer-display-call-app')
  47. autoCallApp.addEventListener('click', (event) => {
  48. event.preventDefault()
  49. event.stopImmediatePropagation()
  50. })
  51.  
  52. const playButton = document.querySelector('.mplayer-pause-call-app')
  53. playButton.addEventListener('click', (event) => {
  54. event.preventDefault()
  55. event.stopImmediatePropagation()
  56. // 播放监听此时还未被阻止
  57. })
  58. }
  59.  
  60. // 全屏后,全屏广告元素样式变为 flex !important
  61. function removeFullscreenAd () {
  62. // 全屏后跳App的横幅、清晰度按钮、弹幕提示
  63. document.addEventListener('fullscreenchange', function () {
  64. const adBanner = document.querySelector('.mplayer-widescreen-callapp')
  65. const qualityBtn = document.querySelector('.mplayer-control-btn-quality')
  66. const text = document.querySelector('.mplayer-comment-text')
  67.  
  68. adBanner.style.cssText = 'display: none !important;'
  69. qualityBtn.style.cssText = 'display: none !important;'
  70. text.style.cssText = 'display: none !important;'
  71.  
  72. const commentContent = document.querySelector('.mplayer-btn-comment-content')
  73. commentContent.style.cssText = 'position: absolute; left: 11px; width: 24px !important;'
  74. })
  75. }
  76.  
  77. function removeAdButton () {
  78. const ad1 = '.home-float-openapp, .open-app, .m-nav-openapp, .m-float-openapp, [class^="m-video2-awaken-btn"]'
  79. // 两个底部出现的弹窗和一个点击播放后的弹窗
  80. const ad2 = '.openapp-dialog, .caution-dialog, .v-dialog'
  81. const style = document.createElement('style')
  82. style.textContent = `${ad1}, ${ad2}{ display: none !important; }`
  83.  
  84. // 如果 document.head 可用,将样式添加到文档
  85. document.head ? document.head.appendChild(style) : waitDOMContentLoaded(document.head.appendChild(style))
  86. }
  87.  
  88. function customElementStyle () {
  89. // 全屏跳App、倍速按钮、播完推荐
  90. const css1 = `
  91. .mplayer-fullscreen-call-app, .mplayer-control-btn-speed, .mplayer-ending-panel-recommend
  92. { display: none !important; }
  93. `
  94.  
  95. // 优化视觉
  96. const css2 = `
  97. /* 调整分集高度 */
  98. .m-video-part-panel-content { height: 81vmin !important; }
  99. /* 阻止跳转APP */
  100. .launch-app-btn { pointer-events: none; }
  101. .card-box a { pointer-events: auto; }
  102. /* 重复的初始图形层 */
  103. .natural-module, .m-footer { display: none !important; }
  104. `
  105.  
  106. // 居中重播按钮
  107. const css3 = '.mplayer-ending-panel-buttons { align-self: center !important; img { margin-left: 3px !important; } }'
  108.  
  109. // 声音按钮
  110. const unmuteStyle = `
  111. .unmute {
  112. position: absolute;
  113. top: 0;
  114. left: 0;
  115. z-index: 1001;
  116. text-transform: uppercase;
  117. color: #000;
  118. font-size: 127%;
  119. font-weight: 500;
  120. background: none;
  121. padding: 12px;
  122. border: 0;
  123. text-align: inherit;
  124. }
  125. .unmute-inner {
  126. position: relative;
  127. width: 100%;
  128. }
  129. .unmute-icon {
  130. width: 48px;
  131. height: 48px;
  132. display: inline-block;
  133. vertical-align: middle;
  134.  
  135. padding-left: 2px;
  136. position: relative;
  137. z-index: 10;
  138. background-color: rgb(255, 255, 255);
  139. border-radius: 2px;
  140. border-bottom: 1px solid #f1f1f1;
  141. }
  142. .unmute svg {
  143. filter: drop-shadow(0 0 2px rgba(0,0,0,.5));
  144. }
  145. .unmute-text {
  146. position: relative;
  147. z-index: 10;
  148. padding-top: 1px;
  149. padding-right: 10px;
  150. max-width: 200px;
  151. white-space: nowrap;
  152. overflow: hidden;
  153. text-overflow: ellipsis;
  154. vertical-align: middle;
  155. display: inline-block;
  156.  
  157. /* animation: unmute-alpha-anim .25s cubic-bezier(.4,0,1,1) 5.4s reverse forwards; */
  158. transition: opacity .25s cubic-bezier(.4,0,1,1);
  159. }
  160. .animated .unmute-text {
  161. opacity: 0;
  162. }
  163. .unmute-box {
  164. width: 100%;
  165. display: block;
  166. background-color: rgb(255, 255, 255);
  167. position: absolute;
  168. left: 0;
  169. top: 0;
  170. bottom: 0;
  171. border-radius: 2px;
  172. border-bottom: 1px solid #f1f1f1;
  173.  
  174. /* animation: unmute-width-anim .5s cubic-bezier(.4,0,1,1) 5.5s reverse forwards; */
  175. transition: width .5s cubic-bezier(.4,0,1,1);
  176. }
  177. .animated .unmute-box {
  178. width: 0;
  179. }
  180. `
  181.  
  182. const style = document.createElement('style')
  183. style.textContent = css1 + css2 + css3 + unmuteStyle
  184.  
  185. // 如果 document.head 可用,将样式添加到文档
  186. document.head ? document.head.appendChild(style) : waitDOMContentLoaded(document.head.appendChild(style))
  187. }
  188.  
  189. function goToVideoById (keyword, callback) {
  190. const callbackName = `jsonp_callback_${Date.now()}_${Math.floor(Math.random() * 100000)}`
  191.  
  192. window[callbackName] = function (responseData) {
  193. if (responseData.data.result[11].data[0]) {
  194. const bvId = responseData.data.result[11].data[0].bvid
  195. callback(bvId, null)
  196. } else {
  197. callback(null, 'BVId not found')
  198. }
  199. delete window[callbackName]
  200. }
  201.  
  202. const script = document.createElement('script')
  203. script.src = `https://api.bilibili.com/x/web-interface/search/all/v2?page=1&keyword=${keyword}&jsonp=jsonp&callback=${callbackName}`
  204. document.body.appendChild(script)
  205. }
  206.  
  207. function addTargetElementListener (targetElement) {
  208. if (targetElement) {
  209. const anchor = targetElement.firstChild
  210. const h2Element = anchor.lastChild
  211. const keyword = encodeURIComponent(h2Element.innerHTML)
  212.  
  213. anchor.addEventListener('click', event => {
  214. event.preventDefault()
  215. event.stopImmediatePropagation()
  216.  
  217. goToVideoById(keyword, (bvId, error) => {
  218. if (bvId) {
  219. const videoUrl = `https://m.bilibili.com/video/${bvId}`
  220. window.history.pushState({}, '', videoUrl)
  221. window.location.href = videoUrl
  222. } else {
  223. console.error('BVId wrong: ', error)
  224. }
  225. })
  226. }, true)
  227. }
  228. console.log('Execute Video! 添加监听器')
  229. }
  230.  
  231. // 自动播放
  232. function autoplay () {
  233. const play = document.querySelector('.main-cover')
  234.  
  235. const style = document.createElement('style')
  236. style.textContent = '.m-navbar + div { display: block !important }'
  237. document.head.appendChild(style)
  238.  
  239. if (play) {
  240. const video = document.querySelector('video')
  241. if (video) {
  242. // 先添加播放监听,第一次播放时执行
  243. video.addEventListener('play', function () {
  244. if (video.muted === true) createUnmuteButton()
  245. }, { once: true })
  246.  
  247. const startPlayPromise = video.play()
  248.  
  249. if (startPlayPromise !== undefined) {
  250. startPlayPromise
  251. .catch((error) => {
  252. if (error.name === 'NotAllowedError') {
  253. video.muted = true
  254. video.play()
  255. } else {
  256. // 处理加载或播放错误
  257. }
  258. })
  259. .then(() => {
  260. // 仅在播放开始后才开始执行你需要执行的操作。
  261. })
  262. }
  263.  
  264. // 创建解除静音按钮
  265. function createUnmuteButton () {
  266. // 检查是否已存在解除静音按钮
  267. if (document.getElementById('unmuteButton')) {
  268. return // 如果已存在,不进行重复创建
  269. }
  270. // 创建按钮元素
  271. const button = document.createElement('button')
  272. button.classList.add('unmute')
  273. button.innerHTML = `
  274. <div class="unmute-inner">
  275. <div class="unmute-icon"><svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
  276. <use class="svg-shadow" xlink:href="#ytp-id-1"></use>
  277. <path class="ytp-svg-fill"
  278. 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"
  279. id="id-1"></path>
  280. </svg></div>
  281. <div class="unmute-text">点按取消静音</div>
  282. <div class="unmute-box"></div>
  283. </div>
  284. `
  285. // 按钮点击事件
  286. button.addEventListener('click', function () {
  287. video.muted = false
  288. button.remove()
  289. })
  290.  
  291. // 将按钮添加到页面的合适位置
  292. const videoWrapper = document.querySelector('.mplayer-video-wrap')
  293. videoWrapper.insertAdjacentElement('afterend', button)
  294. // 第一个参数是函数,而非调用函数的结果
  295. setTimeout(() => {
  296. button.classList.add('animated')
  297. }, 4500)
  298. }
  299. }
  300. }
  301.  
  302. observeCardBox()
  303. }
  304.  
  305. // 观察推荐视频的加载
  306. function observeCardBox () {
  307. const cardBox = document.querySelector('.card-box')
  308. const targetElements = cardBox.children
  309.  
  310. // 为初始子元素添加监听器
  311. Array.from(targetElements).forEach(addTargetElementListener)
  312.  
  313. // 创建 MutationObserver 以监听子元素的变化
  314. const observer = new MutationObserver((mutations) => {
  315. mutations.forEach((mutation) => {
  316. if (mutation.type === 'childList') {
  317. mutation.addedNodes.forEach((addedNode) => {
  318. addTargetElementListener(addedNode)
  319. })
  320. }
  321. })
  322. })
  323.  
  324. // 配置观察选项
  325. const observerConfig = {
  326. childList: true
  327. }
  328.  
  329. // 开始观察
  330. observer.observe(cardBox, observerConfig)
  331. }
  332.  
  333. function addHomeTargetElementListener (tag) {
  334. tag.addEventListener('click', async (event) => { // 异步,然后放到 js 末尾执行
  335. event.preventDefault() // 阻止默认行为
  336. // event.stopPropagation(); // 阻止事件冒泡
  337. event.stopImmediatePropagation() // 阻止其他事件监听器的执行
  338. console.log('test href: ', tag.getAttribute('href'))
  339.  
  340. // 等待下一个事件循环迭代
  341. await new Promise((resolve) => setTimeout(resolve, 0)) // THIS!
  342. window.location.href = tag.getAttribute('href')
  343. }, true)
  344. }
  345.  
  346. function runHome () {
  347. // observeHomeCardBox
  348. const cardBox = document.querySelector('.card-box')
  349. const aTags = cardBox.children
  350.  
  351. Array.from(aTags).forEach(addHomeTargetElementListener)
  352.  
  353. const observer = new MutationObserver((mutations) => {
  354. mutations.forEach((mutation) => {
  355. if (mutation.type === 'childList') {
  356. mutation.addedNodes.forEach((addedNode) => {
  357. addHomeTargetElementListener(addedNode)
  358. })
  359. }
  360. })
  361. })
  362.  
  363. const observerConfig = {
  364. childList: true
  365. }
  366.  
  367. observer.observe(cardBox, observerConfig)
  368.  
  369. console.log('Execute Home!')
  370. }
  371. })()

QingJ © 2025

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