- // ==UserScript==
- // @name 移动端扫地机
- // @namespace https://gf.qytechs.cn/
- // @version 0.0.2-BETA
- // @description F**k!クッソ
- // @author fpschen
- // @homepage https://gf.qytechs.cn/zh-CN/users/256892-fork
- // @match *://*.zhihu.com/*
- // @match *://m.bilibili.com/*
- // @icon https://www.bilibili.com/favicon.ico
- // @grant GM_addStyle
- // @grant unsafeWindow
- // @run-at document_idle
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- function autoCatch(caller, catcher, quiet=false) {
- return function() {
- try {
- return caller.apply(this, arguments)
- } catch(e) {
- catcher && catcher(e)
- if (!quiet) throw e
- }
- }
- }
-
- function reduceCall(...funcs) {
- return function() {
- funcs.reduce((p, c) => autoCatch(c, null, true)(), null)
- }
- }
-
- function listenHistory(name, listener) {
- const origin = history[name]
- history[name] = function() {
- const res = origin.apply(this, arguments)
- autoCatch(listener, null, true)(arguments)
- return res
- }
- }
-
- function findEle(selector, multi = false, container = document) {
- let finder = container.querySelector
- if (multi) {
- finder = container.querySelectorAll
- }
- return finder.call(container, selector)
- }
-
- async function waitEle(selector, { multi, timeout, quiet, container } = {}) {
- multi = multi ?? false
- timeout = timeout ?? 30000
- container = container ?? document
- const start = Date.now()
- return new Promise((resolve, reject) => {
- let checker = () => {
- const el = findEle(selector, multi, container)
- if (el == undefined || (multi && el.length == 0)) {
- if (Date.now() - start > timeout) {
- throw new Error(`[selector](${selector}): timeout!`)
- }
- setTimeout(checker, 100)
- return
- }
- resolve(el)
- }
- checker = autoCatch(checker, reject, quiet)
- checker()
- })
- }
-
- function createUnmuteButton() {
- if (document.getElementById('unmuteButton')) return
-
- const video = document.querySelector('video')
- if (!video.muted) {
- return
- }
-
- GM_addStyle(`
- /*
- * 声音按钮 *
- */
-
- .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 button = document.createElement('button')
- button.classList.add('unmute')
- button.id = 'unmuteButton'
- 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)
- }
-
- // 清除APP引导弹窗
- async function clearModal() {
- GM_addStyle(`
- .OpenInAppButton, .home-float-openapp, .m-video2-awaken-btn {
- display: none!important;
- }
-
- `)
-
- const selectors = ['.MobileModal-wrapper button.Button--secondary', '.Modal-wrapper button.Modal-closeButton']
-
- const el = await Promise.any(selectors.map(selector => waitEle(selector, { quiet: true })))
-
- el?.click()
- }
-
- // 自动【展开阅读】
- function autoExpand() {
- GM_addStyle(`
- .Post-RichTextContainer div:has(.ContentItem-expandButton):not(:has(div)) {
- display: none!important;
- }
- button.ContentItem-expandButton {
- display: none!important;
- }
- .RichContent-inner--collapsed {
- max-height: unset!important;
- mask-image: unset!important;
- --webkit-mask-image: unset!important;
- }
- `)
- }
-
- // 清除B站推荐视频打开APP
- async function directJump() {
- const cards = await waitEle('.v-card-toapp', { multi: true, quiet: true })
-
- function assignUrl(card) {
- const vm = card?.__vue__
- const info = vm?.info
- vm?.$set(vm?.info, 'url', `https://${location.host}/video/${info?.bvid}`)
- }
-
- cards?.forEach(assignUrl)
- }
-
- // 自动播放
- async function autoPlay() {
-
- const selectors = ['.natural-main-video', '.m-video-player']
-
- const video = await Promise.any(selectors.map(selector => waitEle(selector, { quiet: true })))
- const vm = video?.__vue__
- vm?.$set(vm, 'open', true)
- vm?.$set(vm, 'bsource', 'search_google')
- vm?.$emit('trigglePlay')
-
- player.on('video_media_play', createUnmuteButton)
-
- await bindPlaybackspeed()
-
- }
-
- // 自动关闭弹框
- async function autoCloseDialog() {
- const closeBtn = await waitEle('.openapp-dialog .dialog-close', { quiet: true })
- closeBtn?.click()
- }
-
- async function bindPlaybackspeed() {
- const btn = await waitEle('.mplayer-control-btn-speed', { quiet: true, timeout: 500 })
- if (btn.__bound) {
- return
- }
-
- btn.__bound = true
-
- const speedList = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]
-
- const speedContainer = document.createElement('div')
- const containerList = speedContainer.classList
- containerList.add('speed-control-container', 'display-none')
- btn.appendChild(speedContainer)
-
- btn.addEventListener('click', () => {
- if (containerList.contains('display-none')) {
- containerList.remove('display-none')
- } else {
- containerList.add('display-none')
- }
- })
-
- function speedClick({ target }) {
- const video = document.querySelector('video')
- const speed = Number(target.getAttribute('data-speed'))
- if (video && speed > 0) {
- video.playbackRate = speed
- }
- }
-
- function addSpeed(speed, container) {
- const speedSpan = document.createElement('span')
- speedSpan.innerText = `${speed} X`
- speedSpan.setAttribute('data-speed', speed)
- speedSpan.addEventListener('click', speedClick)
- container.insertAdjacentElement('afterbegin', speedSpan)
- }
-
- speedList.map(speed => addSpeed(speed, speedContainer))
-
- GM_addStyle(`
- .speed-control-container {
- display: flex;
- position: absolute;
- font-size: var(--show-size);
- color: white;
- flex-direction: column;
- transform: translateY(-75%);
- max-height: var(--show-height);
- overflow-y: scroll;
- background-color: rgba(0, 0, 0, 0.5);
- padding: .2rem;
- padding-right: .3rem;
- border-radius: .4rem;
- white-space: pre;
- text-align: right;
-
- --show-count: 8;
- --total-count: ${speedList.length};
- --show-size: .8rem;
- --show-height: calc(var(--show-count) * var(--show-size));
- }
- .display-none {
- display: none;
- }
- `)
-
- }
-
- async function observeCardBox(listen = true) {
-
-
- if (listen) {
- const changeListener = reduceCall(observeCardBox.bind(null, !listen), autoPlay)
-
- listenHistory('pushState', changeListener)
- listenHistory('replaceState', changeListener)
- }
-
- const videoList = await waitEle('.video-list', { quiet: true })
- const vm = videoList?.__vue__
- vm?.$watch('list', () => {
- directJump()
- })
- const state = vm?.$store?.state
-
- const common = state?.common
- if (common) {
- // common.noCallApp = true
- }
-
- const search = state?.search
- if (search) {
- search.openAppDialog = false
- }
- const video = state?.video
- if (video) {
- video.isClient = true
- }
-
- if (PlayerAgent) {
- PlayerAgent.openApp = function() {
- console.log('call open app')
- }
- }
- }
-
- autoCatch(clearModal, null, true)()
- autoExpand()
- directJump()
- autoPlay()
- autoCloseDialog()
- observeCardBox()
- })();