过滤爱壹帆上的广告
// ==UserScript==
// @name IYF Ad Filter
// @name:zh-CN 爱壹帆广告过滤器
// @name:zh-TW 愛壹帆廣告過濾器
// @description Filter ads on iyf.tv
// @description:zh-CN 过滤爱壹帆上的广告
// @description:zh-TW 過濾愛壹帆上的廣告
// @version 0.3.27
// @namespace https://github.com/dzist
// @author Dylan Zhang
// @include *://*.iyf.tv/*
// @include *://*.yifan.tv/*
// @include *://*.yfsp.tv/*
// @include *://*.aiyifan.tv/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=iyf.tv
// @license MIT
// @run-at document-end
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
/* utilities */
const utils = {
log(message) {
console.log(
'%cIYF Ad Filter',
'padding: 0 4px; background: cyan; color: #000;',
message
)
},
ensureCondition(condition, maxAttempts = 600 /* 10s */, failureMessage) {
return new Promise((resolve, reject) => {
let attempts = 0
const detect = () => {
const result = condition()
if (result) {
resolve(result)
} else if (attempts < maxAttempts) {
attempts++
requestAnimationFrame(detect)
} else {
reject(new Error(failureMessage))
}
}
requestAnimationFrame(detect)
})
},
ensureElement(selector, maxAttempts = 600) {
return utils.ensureCondition(
() => document.querySelector(selector),
maxAttempts,
`Could not detect ${selector} after ${maxAttempts} attempts`
)
},
fnOnce(fn) {
let done = false
return function() {
if (done) return
done = true
fn.apply(this, arguments)
}
},
createEmptyArray() {
const arr = []
const proto = Object.create(Array.prototype)
proto.push = function() { return 0 }
Object.setPrototypeOf(arr, proto)
return arr
},
getProperty(obj, ...props) {
const stack = [obj]
let current = obj
for(let i = 0, len = props.length; i < len; i++) {
const key = props[i]
// check for undefined or null
if (current == null) {
return [, stack]
}
const isArraySearch = Array.isArray(current) && isNaN(Number(key))
if (isArraySearch) {
// if the `current` value is an array and the key is not a number,
// find the first item with the key inside it.
const foundItem = current.find(item => item && item[key])
if (foundItem) {
stack.push(foundItem)
current = foundItem[key]
} else {
return [, stack]
}
} else {
current = current[key]
if (i < len - 1 && current) {
stack.push(current)
}
}
}
return [current, stack]
}
}
/* router */
class Router {
constructor() {
this.routes = new Map()
this.beforeFns = new Set()
this.overrideHistory()
}
overrideHistory() {
const { history } = window
const hook = () => this.handle()
const override = (fn) => {
return (...args) => {
const result = fn.apply(history, args)
hook()
return result
}
}
history.pushState = override(history.pushState)
history.replaceState = override(history.replaceState)
window.addEventListener('popstate', hook)
}
// this.use(path: string | () => void, fn?: () => void): Router
use(path, fn) {
let matcher
if (typeof path === 'string') {
matcher = this.getMatcher(path)
if (!matcher) {
matcher = {
path,
regexp: new RegExp(`^${path}$`)
}
}
this.currentMatcher = matcher
}
if (typeof path === 'function') {
fn = path
matcher = this.currentMatcher
}
if (fn) {
const fns = this.routes.get(matcher)
fns
? fns.add(fn)
: this.routes.set(matcher, new Set([fn]))
}
return this
}
getMatcher(path) {
return this.routes
.keys()
.find(m => m.path === path)
}
once(path, fn) {
if (typeof path === 'string' && !fn) {
throw new Error(
`It is not the path, but the corresponding function that can only be used once.` +
`Please specify a callback function for the path ${path}`
)
}
if (typeof path === 'function') {
return this.use(utils.fnOnce(path))
}
return this.use(path, utils.fnOnce(fn))
}
before(fn) {
this.beforeFns.add(fn)
return this
}
beforeOnce(fn) {
return this.before(utils.fnOnce(fn))
}
handle(pathname = this.pathname) {
this.beforeFns.forEach(fn => fn())
for(const [matcher, fns] of this.routes) {
if (matcher.regexp.test(pathname)) {
fns.forEach(fn => fn())
}
}
}
get pathname() {
const path = location.pathname.split('/')[1]
return path ? `/${path}` : '/'
}
}
/* shortcuts */
class VideoShortcuts {
constructor(vgAPI) {
this.vgAPI = vgAPI
this.seekStep = 5
this.volumeStep = 0.1
this.bindEvents()
}
bindEvents() {
window.addEventListener('keydown', this.handleKeyDown)
window.addEventListener('beforeunload', this.handleBeforeUnload)
}
handleKeyDown = (event) => {
if (this.isTyping || !this.vgAPI) return
switch(event.key) {
case '1':
case '2':
case '3':
case '4':
case '5':
this.setPlaybackRate(parseInt(event.key))
break
case 'w':
this.setVolume(Math.min(this.currentVolume + this.volumeStep, 1))
break
case 's':
this.setVolume(Math.max(this.currentVolume - this.volumeStep, 0))
break
case 'a': // rewind
this.seek(this.currentTime - this.seekStep)
break
case 'd': // fast forward
this.seek(this.currentTime + this.seekStep)
break
case 'f': // fullscreen
this.toggleFullscreen()
break
}
}
handleBeforeUnload = () => {
window.removeEventListener('keydown', this.handleKeyDown)
window.removeEventListener('beforeunload', this.handleBeforeUnload)
this.vgAPI = null
}
get isFullscreen() {
return this.vgAPI.fsAPI.isFullscreen
}
toggleFullscreen() {
this.isFullscreen
? this.exitFullscreen()
: this.requestFullscreen()
}
requestFullscreen() {
this.vgAPI.fsAPI.request()
}
exitFullscreen() {
this.vgAPI.fsAPI.exit()
}
get currentTime() {
return this.vgAPI.currentTime
}
seek(time) {
this.vgAPI.currentTime = time
}
get currentVolume() {
return this.vgAPI.volume
}
setVolume(volume) {
this.vgAPI.volume = volume
}
setPlaybackRate(rate) {
this.vgAPI.playbackRate = rate
}
get isTyping() {
const activeElement = document.activeElement
return activeElement instanceof HTMLInputElement
|| activeElement instanceof HTMLTextAreaElement
|| activeElement.isContentEditable
}
}
/* pages */
const router = new Router()
router.once('/', () => {
const indexStyle = `
.sliders .sec4,
.sliders .sec4 + .separater,
app-index app-recommended-news:has(a[href="/svideo"]),
app-index app-classified-top-videos:has(a[href="/movie"]) > app-home-collection,
app-index app-classified-top-videos:has(a[href*="/list/short"]),
app-index div:has(> app-discovery-in-home),
app-index .new-list {
display: none!important;
}
`
GM_addStyle(indexStyle)
})
let videoShortcuts = null
router.use('/(play|watch)')
.once(() => {
const playStyle = `
.video-player > div:last-child,
.video-player vg-pause-f,
.video-player .overlay-logo,
.video-player .publicbox,
.video-player .quanlity-items .use-coin-box {
display: none!important;
}
.video-player .player-title {
margin-left: 0!important;
}
.video-player + div.ps > div.bl {
display: none!important;
}
.main div.playPageTop {
min-height: 594px!important;
}
`
GM_addStyle(playStyle)
// shortcuts
videoShortcuts = new VideoShortcuts()
})
.use(() => {
const { log, ensureElement, getProperty, createEmptyArray } = utils
Promise.all([
ensureElement('aa-videoplayer'),
ensureElement('vg-player'),
ensureElement('.action-pannel i')
]).then(([
aaVideoPlayerEl,
vgPlayerEl,
danmuBtnEl
]) => {
// close danmu
if (danmuBtnEl.classList.contains('icondanmukai')) {
danmuBtnEl.click()
}
const contextKey = '__ngContext__'
// remove pause ads for 20s
const [pgmp] = getProperty(aaVideoPlayerEl, contextKey, 'pgmp')
if (pgmp) {
pgmp.dataList = createEmptyArray()
} else {
log('pgmp not found')
}
// shortcuts
const [vgAPI] = getProperty(vgPlayerEl, contextKey, 'API')
if (vgAPI) {
videoShortcuts.vgAPI = vgAPI
} else {
log('vgAPI not found')
}
})
})
router.once('/(list|search)', () => {
const listStyle = `
#filterDiv,
.filters {
width: 100%;
}
.filters + div.ss-ctn {
display: none!important;
}
`
GM_addStyle(listStyle)
})
router.beforeOnce(() => {
const commonStyle = `
a:has(img[alt="广告"]),
a[href="https://www.wyav.tv/"],
i.vip-label {
display: none!important;
}
.navbar .multi-top-buttons,
.navbar app-dn-user-menu-item.top-item,
.navbar .login-inner-box,
.navbar .menu-item:has(a[href="https://www.wyav.tv/"]) {
display: none!important;
}
.navbar .menu-pop.two-col {
width: 160px!important;
left: 0!important;
}
.navbar .my-card.none-user,
.navbar .none-user-content {
height: auto!important;
}
.login-frame-container .gg-dl {
display: none!important;
}
.login-frame-container .login-frame-box.heighter,
.login-frame-container .inner {
width: auto!important;
margin-left: 0px!important;
}
#sticky-block .inner {
display: none!important;
}
`
GM_addStyle(commonStyle)
})
function main() {
router.handle()
}
main()
})();