IYF Ad Filter

Filter ads on iyf.tv

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

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.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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.28
// @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(...args) {
            console.log(
                '%cIYF Ad Filter',
                'padding: 0 4px; background: cyan; color: #000;',
                ...args
            )
        },
        ensureCondition(condition, maxAttempts = 600 /* 10s */, failureMessage) {
            const raf = window.requestAnimationFrame

            return new Promise((resolve, reject) => {
                let attempts = 0
                const detect = () => {
                    const result = condition()
                    if (result) {
                        resolve(result)
                    } else if (attempts < maxAttempts) {
                        attempts++
                        raf(detect)
                    } else {
                        reject(new Error(failureMessage))
                    }
                }
                raf(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.currentMatcher = null
            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())
                    break
                }
            }
        }
        get pathname() {
            const path = location.pathname.split('/')[1]
            return path ? `/${path}` : '/'
        }
    }

    /* shortcuts */
    class VideoShortcuts {
        // Ideally, private static fields/methods should be used,
        // but the public ones are required here for compatibility.
        static instance = null
        static canNew = false
        static toggleNew(value) {
            this.canNew = value
        }
        // Must be a public static method for the user to call
        static getInstance(...args) {
            if (!this.instance) {
                this.toggleNew(true)
                this.instance = new VideoShortcuts(...args)
                this.toggleNew(false)
            }
            return this.instance
        }

        constructor(vgAPI) {
            if (!VideoShortcuts.canNew) {
                throw new Error(
                    `Forbid to create an instance of the class 'VideoShortcuts' with the 'new' keyword.` +
                    `Please use its static method: 'VideoShortcuts.getInstance(arg1, arg2, ...)`
                )
            }

            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': // increase volume
                    this.setVolume(Math.min(this.currentVolume + this.volumeStep, 1))
                    break
                case 's': // decrease volume
                    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)
    })

    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)
    })
    .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 the 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.getInstance().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()
})();