動畫瘋-自動觀看廣告

自動觀看動畫瘋廣告

// ==UserScript==
// @name         動畫瘋-自動觀看廣告
// @namespace    https://shinoharahare.github.io/
// @version      0.4
// @description  自動觀看動畫瘋廣告
// @author       Hare
// @match        https://ani.gamer.com.tw/animeVideo.php?sn=*
// @match        https://imasdk.googleapis.com/js/core/bridge*
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.addValueChangeListener
// @grant        GM.registerMenuCommand
// @grant        GM.unregisterMenuCommand
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@9
// ==/UserScript==

(async () => {
    'use strict';

    window.config = new Proxy({
        skipTime: 30,
        pauseOnSkipped: true,
        muteAd: true
    }, {
        async get(target, key) {
            if (key in target) {
                let type = target[key].constructor
                let val = await GM.getValue(key, target[key])
                return type(val)
            } else {
                throw 'Unknown Config'
            }
        },
        async set(target, key, value) {
            if (key in target) {
                await GM.setValue(key, value)
            } else {
                throw 'Unknown Config'
            }
            return true
        }
    })

    if (location.href.includes('ani.gamer.com.tw')) {
        if (await hasAd()) {
            let btn = await wait(() => document.querySelector('#adult'))
            btn.click()

            if (hasImaAd()) {
                registerMessageHandlerTop()
                let video = document.querySelector('video[title=Advertisement]')
                if (await config.muteAd) {
                    video.addEventListener('play', ({ target }) => target.muted = true)
                }
                await sleep(await config.skipTime * 1000)
                await postMessage2Ima({ type: 'skip' })
            }

            let player = videojs.getPlayer('ani_video')

            if (!skipped()) {
                if (await config.muteAd) {
                    player.muted(true)
                }
                let skipBtn = await wait(() => document.querySelector('div[class*="skip"]'), skipped)
                await wait(() => skipBtn.innerText.includes('跳過'), skipped)
                skipBtn.click()
            }

            if (await config.pauseOnSkipped) {
                const video = document.querySelector('#ani_video_html5_api')
                video.addEventListener('loadeddata', () => {
                    const t = () => video.pause() || video.removeEventListener('play', t)
                    video.addEventListener('play', t)
                })
            }

            if (await config.muteAd) {
                player.muted(false)
            }
        }
    } else if (location.href.includes('imasdk.googleapis.com')) {
        if (window.self != window.top) {
            registerMessageHandlerIma()
        }
    }
})();

(async () => {
    if (window.self === window.top) {
        let ids = []
        let names = ['pauseOnSkipped', 'muteAd']

        async function loadMenu() {
            for (let id of ids) {
                GM.unregisterMenuCommand(id)
            }
            ids = []

            registerMenuCommand('修改跳過秒數', async () => {
                const { isConfirmed, value } = await swal.fire({
                    title: '修改跳過秒數',
                    input: 'number',
                    inputValue: await config.skipTime,
                    showCancelButton: true,
                })
                if (isConfirmed) {
                    config.skipTime = value
                }
            }, 'D')

            registerSwitch('跳過後暫停', 'pauseOnSkipped', 'P')
            registerSwitch('靜音廣告', 'muteAd', 'M')
        }

        async function registerMenuCommand(...args) {
            const id = await GM.registerMenuCommand(...args)
            ids.push(id)
        }

        async function registerSwitch(title, name, accessKey) {
            registerMenuCommand(`[${await config[name] ? '停用' : '啟用'}] ${title}`, async() => config[name] = !await config[name], accessKey)
        }

        for (let name of names) {
            GM.addValueChangeListener(name, () => loadMenu())
        }

        loadMenu()
    }
})();


function hasImaAd() {
    return document.querySelector('video[title=Advertisement]') != null
}

function skipped() {
    return videojs.getPlayer('ani_video').adFinished
}

function registerMessageHandlerTop() {
    window.resolves = {}
    window.addEventListener('message', ({ data }) => {
        const id = data._id
        if (id in window.resolves) {
            window.resolves[id]()
            delete window.resolves[id]
        }
    })
}

function registerMessageHandlerIma() {
    window.addEventListener('message', ({ data, source }) => {
        if (data._id) {
            switch (data.type) {
                case 'skip': {
                    document.querySelector('.videoAdUiSkipButton').click()
                    break
                }
            }
            source.postMessage(data, 'https://ani.gamer.com.tw')
        }
    })
}


function postMessage2Ima(msg) {
    const win = document.querySelector('iframe[src^="https://imasdk.googleapis.com/js/core/"]').contentWindow
    return new Promise(resolve => {
        const id = performance.now()
        window.resolves[id] = resolve
        win.postMessage({
            _id: id,
            ...msg
        }, 'https://imasdk.googleapis.com')
    })
}


async function hasAd() {
    const res = await fetch(`/ajax/token.php?sn=${animefun.videoSn}`)
    const json = await res.json()
    return json.time == 0
}


function wait(condition, canceler, responser, tick=100) {
    return new Promise((resolve) => {
        let i = setInterval(() => {
            let c = condition()
            if (canceler && canceler()) {
                clearInterval(i)
            } else if (c) {
                clearInterval(i)
                resolve(responser ? responser() : c)
            }
        }, tick);
    })
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}