移动端扫地机

F**k!クッソ

  1. // ==UserScript==
  2. // @name 移动端扫地机
  3. // @namespace https://gf.qytechs.cn/
  4. // @version 0.0.2-BETA
  5. // @description F**k!クッソ
  6. // @author fpschen
  7. // @homepage https://gf.qytechs.cn/zh-CN/users/256892-fork
  8. // @match *://*.zhihu.com/*
  9. // @match *://m.bilibili.com/*
  10. // @icon https://www.bilibili.com/favicon.ico
  11. // @grant GM_addStyle
  12. // @grant unsafeWindow
  13. // @run-at document_idle
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. function autoCatch(caller, catcher, quiet=false) {
  21. return function() {
  22. try {
  23. return caller.apply(this, arguments)
  24. } catch(e) {
  25. catcher && catcher(e)
  26. if (!quiet) throw e
  27. }
  28. }
  29. }
  30.  
  31. function reduceCall(...funcs) {
  32. return function() {
  33. funcs.reduce((p, c) => autoCatch(c, null, true)(), null)
  34. }
  35. }
  36.  
  37. function listenHistory(name, listener) {
  38. const origin = history[name]
  39. history[name] = function() {
  40. const res = origin.apply(this, arguments)
  41. autoCatch(listener, null, true)(arguments)
  42. return res
  43. }
  44. }
  45.  
  46. function findEle(selector, multi = false, container = document) {
  47. let finder = container.querySelector
  48. if (multi) {
  49. finder = container.querySelectorAll
  50. }
  51. return finder.call(container, selector)
  52. }
  53.  
  54. async function waitEle(selector, { multi, timeout, quiet, container } = {}) {
  55. multi = multi ?? false
  56. timeout = timeout ?? 30000
  57. container = container ?? document
  58. const start = Date.now()
  59. return new Promise((resolve, reject) => {
  60. let checker = () => {
  61. const el = findEle(selector, multi, container)
  62. if (el == undefined || (multi && el.length == 0)) {
  63. if (Date.now() - start > timeout) {
  64. throw new Error(`[selector](${selector}): timeout!`)
  65. }
  66. setTimeout(checker, 100)
  67. return
  68. }
  69. resolve(el)
  70. }
  71. checker = autoCatch(checker, reject, quiet)
  72. checker()
  73. })
  74. }
  75.  
  76. function createUnmuteButton() {
  77. if (document.getElementById('unmuteButton')) return
  78.  
  79. const video = document.querySelector('video')
  80. if (!video.muted) {
  81. return
  82. }
  83.  
  84. GM_addStyle(`
  85. /*
  86. * 声音按钮 *
  87. */
  88.  
  89. .unmute {
  90. position: absolute;
  91. top: 0;
  92. padding: 12px;
  93. background: none;
  94. border: 0;
  95. font-size: 127%;
  96. text-align: inherit;
  97. }
  98. .unmute-inner {
  99. position: relative;
  100. }
  101. .unmute-icon {
  102. height: 48px;
  103. display: inline-block;
  104. vertical-align: middle;
  105. padding-left: 2px;
  106. position: relative;
  107. z-index: 10;
  108. background-color: rgb(255, 255, 255);
  109. border-radius: 2px;
  110. border-bottom: 1px solid #f1f1f1;
  111. }
  112. .unmute svg {
  113. filter: drop-shadow(0 0 2px rgba(0,0,0,.5));
  114. }
  115. .unmute-text {
  116. position: relative;
  117. z-index: 10;
  118. padding-right: 10px;
  119. vertical-align: middle;
  120. display: inline-block;
  121. transition: opacity .25s cubic-bezier(.4,0,1,1);
  122. }
  123. .animated .unmute-text {
  124. opacity: 0;
  125. }
  126. .unmute-box {
  127. width: 100%;
  128. background-color: rgb(255, 255, 255);
  129. position: absolute;
  130. top: 0;
  131. bottom: 0;
  132. border-radius: 2px;
  133. border-bottom: 1px solid #f1f1f1;
  134. transition: width .5s cubic-bezier(.4,0,1,1);
  135. }
  136. .animated .unmute-box {
  137. width: 0;
  138. }
  139. `)
  140.  
  141. const button = document.createElement('button')
  142. button.classList.add('unmute')
  143. button.id = 'unmuteButton'
  144. button.innerHTML = `
  145. <div class="unmute-inner">
  146. <div class="unmute-icon"><svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
  147. <use class="svg-shadow" xlink:href="#ytp-id-1"></use>
  148. <path class="ytp-svg-fill"
  149. 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"
  150. id="id-1"></path>
  151. </svg></div>
  152. <div class="unmute-text">点按取消静音</div>
  153. <div class="unmute-box"></div>
  154. </div>
  155. `
  156. button.addEventListener('click', function () {
  157. video.muted = false
  158. button.remove()
  159. })
  160.  
  161. const videoWrapper = document.querySelector('.mplayer-video-wrap')
  162. videoWrapper.insertAdjacentElement('afterend', button)
  163. setTimeout(() => {
  164. button.classList.add('animated')
  165. }, 4500)
  166. }
  167.  
  168. // 清除APP引导弹窗
  169. async function clearModal() {
  170. GM_addStyle(`
  171. .OpenInAppButton, .home-float-openapp, .m-video2-awaken-btn {
  172. display: none!important;
  173. }
  174.  
  175. `)
  176.  
  177. const selectors = ['.MobileModal-wrapper button.Button--secondary', '.Modal-wrapper button.Modal-closeButton']
  178.  
  179. const el = await Promise.any(selectors.map(selector => waitEle(selector, { quiet: true })))
  180.  
  181. el?.click()
  182. }
  183.  
  184. // 自动【展开阅读】
  185. function autoExpand() {
  186. GM_addStyle(`
  187. .Post-RichTextContainer div:has(.ContentItem-expandButton):not(:has(div)) {
  188. display: none!important;
  189. }
  190. button.ContentItem-expandButton {
  191. display: none!important;
  192. }
  193. .RichContent-inner--collapsed {
  194. max-height: unset!important;
  195. mask-image: unset!important;
  196. --webkit-mask-image: unset!important;
  197. }
  198. `)
  199. }
  200.  
  201. // 清除B站推荐视频打开APP
  202. async function directJump() {
  203. const cards = await waitEle('.v-card-toapp', { multi: true, quiet: true })
  204.  
  205. function assignUrl(card) {
  206. const vm = card?.__vue__
  207. const info = vm?.info
  208. vm?.$set(vm?.info, 'url', `https://${location.host}/video/${info?.bvid}`)
  209. }
  210.  
  211. cards?.forEach(assignUrl)
  212. }
  213.  
  214. // 自动播放
  215. async function autoPlay() {
  216.  
  217. const selectors = ['.natural-main-video', '.m-video-player']
  218.  
  219. const video = await Promise.any(selectors.map(selector => waitEle(selector, { quiet: true })))
  220. const vm = video?.__vue__
  221. vm?.$set(vm, 'open', true)
  222. vm?.$set(vm, 'bsource', 'search_google')
  223. vm?.$emit('trigglePlay')
  224.  
  225. player.on('video_media_play', createUnmuteButton)
  226.  
  227. await bindPlaybackspeed()
  228.  
  229. }
  230.  
  231. // 自动关闭弹框
  232. async function autoCloseDialog() {
  233. const closeBtn = await waitEle('.openapp-dialog .dialog-close', { quiet: true })
  234. closeBtn?.click()
  235. }
  236.  
  237. async function bindPlaybackspeed() {
  238. const btn = await waitEle('.mplayer-control-btn-speed', { quiet: true, timeout: 500 })
  239. if (btn.__bound) {
  240. return
  241. }
  242.  
  243. btn.__bound = true
  244.  
  245. const speedList = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]
  246.  
  247. const speedContainer = document.createElement('div')
  248. const containerList = speedContainer.classList
  249. containerList.add('speed-control-container', 'display-none')
  250. btn.appendChild(speedContainer)
  251.  
  252. btn.addEventListener('click', () => {
  253. if (containerList.contains('display-none')) {
  254. containerList.remove('display-none')
  255. } else {
  256. containerList.add('display-none')
  257. }
  258. })
  259.  
  260. function speedClick({ target }) {
  261. const video = document.querySelector('video')
  262. const speed = Number(target.getAttribute('data-speed'))
  263. if (video && speed > 0) {
  264. video.playbackRate = speed
  265. }
  266. }
  267.  
  268. function addSpeed(speed, container) {
  269. const speedSpan = document.createElement('span')
  270. speedSpan.innerText = `${speed} X`
  271. speedSpan.setAttribute('data-speed', speed)
  272. speedSpan.addEventListener('click', speedClick)
  273. container.insertAdjacentElement('afterbegin', speedSpan)
  274. }
  275.  
  276. speedList.map(speed => addSpeed(speed, speedContainer))
  277.  
  278. GM_addStyle(`
  279. .speed-control-container {
  280. display: flex;
  281. position: absolute;
  282. font-size: var(--show-size);
  283. color: white;
  284. flex-direction: column;
  285. transform: translateY(-75%);
  286. max-height: var(--show-height);
  287. overflow-y: scroll;
  288. background-color: rgba(0, 0, 0, 0.5);
  289. padding: .2rem;
  290. padding-right: .3rem;
  291. border-radius: .4rem;
  292. white-space: pre;
  293. text-align: right;
  294.  
  295. --show-count: 8;
  296. --total-count: ${speedList.length};
  297. --show-size: .8rem;
  298. --show-height: calc(var(--show-count) * var(--show-size));
  299. }
  300. .display-none {
  301. display: none;
  302. }
  303. `)
  304.  
  305. }
  306.  
  307. async function observeCardBox(listen = true) {
  308.  
  309.  
  310. if (listen) {
  311. const changeListener = reduceCall(observeCardBox.bind(null, !listen), autoPlay)
  312.  
  313. listenHistory('pushState', changeListener)
  314. listenHistory('replaceState', changeListener)
  315. }
  316.  
  317. const videoList = await waitEle('.video-list', { quiet: true })
  318. const vm = videoList?.__vue__
  319. vm?.$watch('list', () => {
  320. directJump()
  321. })
  322. const state = vm?.$store?.state
  323.  
  324. const common = state?.common
  325. if (common) {
  326. // common.noCallApp = true
  327. }
  328.  
  329. const search = state?.search
  330. if (search) {
  331. search.openAppDialog = false
  332. }
  333. const video = state?.video
  334. if (video) {
  335. video.isClient = true
  336. }
  337.  
  338. if (PlayerAgent) {
  339. PlayerAgent.openApp = function() {
  340. console.log('call open app')
  341. }
  342. }
  343. }
  344.  
  345. autoCatch(clearModal, null, true)()
  346. autoExpand()
  347. directJump()
  348. autoPlay()
  349. autoCloseDialog()
  350. observeCardBox()
  351. })();

QingJ © 2025

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