IYF Ad Filter

Filter ads on iyf.tv

Fra og med 24.10.2025. Se den nyeste version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         IYF Ad Filter
// @namespace    https://github.com/dzist
// @version      0.3.16
// @description  Filter ads on iyf.tv
// @description:zh-CN  过滤广告 on iyf.tv
// @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
// @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.seekStep = 5
            this.volumeStep = 0.1
            this.bindEvents()
        }

        bindEvents() {
            window.addEventListener('keydown', this.handleKeyDown)
            window.addEventListener('beforeunload', this.handleBeforeUnload)
        }
        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 '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 === 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 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;
        }
    `
    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()
})();