// ==UserScript==
// @name IYF Ad Filter
// @namespace http://tampermonkey.net/
// @version 0.3.12
// @description Filter ads on iyf.tv
// @description:zh-CN 过滤广告 on iyf.tv
// @author Dylan Zhang
// @include https://*.iyf.tv/*
// @include https://*.yifan.tv/*
// @include https://*.yfsp.tv/*
// @include https://*.aiyifan.tv/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=iyf.tv
// @license MIT
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
/* utilities */
const utils = {
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`
)
},
getDeps(el, prop) {
for(const key in el) {
if (key.startsWith('__ngContext__')) {
const context = el[key]
for (const item of context) {
if (item && typeof item === 'object' && item[prop]) {
return [item[prop], item]
}
}
}
}
return []
},
delegateArray(arr) {
const delegateProto = Object.create(Array.prototype)
delegateProto.push = function(){ return 0 }
Object.setPrototypeOf(arr, delegateProto)
return arr
}
}
/* router */
class Router {
constructor() {
this.routes = new Map()
this.initialize()
}
initialize() {
const originalPushState = history.pushState
const originalReplaceState = history.replaceState
const pushstateEvent = new Event('pushstate')
const replacestateEvent = new Event('replacestate')
// override pushState
history.pushState = function() {
const result = originalPushState.apply(this, arguments)
window.dispatchEvent(pushstateEvent)
return result
};
// override replaceState
history.replaceState = function() {
const result = originalReplaceState.apply(this, arguments)
window.dispatchEvent(replacestateEvent)
return result
}
;['pushstate', 'replaceState', 'popstate'].forEach(eventName => {
window.addEventListener(eventName, () => this.handle())
})
}
use(path, handler) {
const handlers = this.routes.has(path)
? [...this.routes.get(path), handler]
: [handler]
this.routes.set(path, handlers)
return this
}
once(path, handler) {
let done = false
function fn() {
if (done) return
done = true
handler.apply(this, arguments)
}
this.use(path, fn)
return this
}
handle(pathname) {
if (!pathname) pathname = this.getPathname()
for(const [path, handlers] of this.routes) {
if (
typeof path === 'string' && path === pathname ||
path instanceof RegExp && path.test(pathname)
) {
handlers.forEach(fn => fn())
return
}
}
}
getPathname() {
const path = location.pathname.split('/')[1]
return path ? `/${path}` : '/'
}
}
/* shortcuts */
class VideoShortcuts {
constructor(vgAPI) {
this.vgAPI = vgAPI
this.step = 5
this.bindEvents()
}
bindEvents() {
window.addEventListener('keydown', this.handleKeyDown.bind(this))
}
handleKeyDown(event) {
if (this.isTyping()) return
if (!this.vgAPI) return
switch(event.key) {
case '1':
case '2':
case '3':
case '4':
case '5':
this.setPlaybackRate(parseInt(event.key))
break
case 'a': // rewind
this.seek(this.getCurrentTime() - this.step)
break
case 'd': // fast forward
this.seek(this.getCurrentTime() + this.step)
break
case 'f': // fullscreen
this.toggleFullscreen()
break
}
}
toggleFullscreen() {
this.isFullscreen()
? this.exitFullscreen()
: this.requestFullscreen()
}
isFullscreen() {
return this.vgAPI.fsAPI.isFullscreen
}
requestFullscreen() {
this.vgAPI.fsAPI.request()
}
exitFullscreen() {
this.vgAPI.fsAPI.exit()
}
seek(time) {
this.vgAPI.currentTime = time
}
getCurrentTime() {
return this.vgAPI.currentTime
}
setPlaybackRate(rate) {
this.vgAPI.playbackRate = rate
}
isTyping() {
const activeElement = document.activeElement
return activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement ||
activeElement.isContentEditable === true
}
}
/* page */
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;
}
`
const indexStyle = `
.sliders .sec4,
.sliders .sec4 + .separater,
app-index app-recommended-news:nth-of-type(2),
app-index app-classified-top-videos:nth-of-type(1) > app-home-collection,
app-index div:has(> app-discovery-in-home),
app-index .new-list {
display: none!important;
}
`
const playStyle = `
.video-player > div:last-child,
.video-player .overlay-logo,
.video-player vg-pause-f,
.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;
}
`
const listStyle = `
#filterDiv,
.filters {
width: 100%;
}
.filters + div.ss-ctn {
display: none!important;
}
`
const router = new Router()
router.once('/', () => {
GM_addStyle(indexStyle)
})
const playRE = /^\/(play|watch)/
let videoShortcuts = null
router.once(playRE, () => {
GM_addStyle(playStyle)
// shortcuts
videoShortcuts = new VideoShortcuts()
})
.use(playRE, () => {
const { ensureElement, getDeps, delegateArray } = utils
Promise.all([
ensureElement('aa-videoplayer'),
ensureElement('vg-player'),
ensureElement('.action-pannel i')
]).then(([
aaVideoPlayerEl,
vgPlayerEl,
danmuEl
]) => {
// close danmu
if (danmuEl.classList.contains('icondanmukai')) {
danmuEl.click()
}
// remove pause ads for 20s
const [pgmp] = getDeps(aaVideoPlayerEl, 'pgmp')
if (pgmp) {
pgmp.dataList = delegateArray([])
} else {
console.warn('pgmp not found')
}
// shortcuts
const [vgAPI] = getDeps(vgPlayerEl, 'API')
if (vgAPI) {
videoShortcuts.vgAPI = vgAPI
} else {
console.warn('vgAPI not found')
}
})
})
router.once(/^\/(list|search)/, () => {
GM_addStyle(listStyle)
})
function init() {
GM_addStyle(commonStyle)
router.handle()
}
init()
})();