動畫瘋-自動跳過廣告

自動跳過動畫瘋廣告

// ==UserScript==
// @name         動畫瘋-自動跳過廣告
// @namespace    https://shinoharahare.github.io/
// @version      1.7.1
// @description  自動跳過動畫瘋廣告
// @author       Hare
// @match        https://ani.gamer.com.tw/animeVideo.php?sn=*
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM.deleteValue
// @grant        GM.addValueChangeListener
// @grant        GM.info
// @grant        GM.registerMenuCommand
// @grant        GM.unregisterMenuCommand
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@9
// @connect      api.gamer.com.tw
// ==/UserScript==

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

    toastr.options = { positionClass: 'toast-top-center' }

    await loadConfig()
    setupMenu()

    hookAjax('/ajax/token.php', async (ajax, opts) => {
        let token = await get(opts.url)
        token.time = 1
        opts.success(JSON.stringify(token))
    })

    if (config.useMobileAPI) {
        mobile()
    } else {
        web()
    }

    HTMLElement.prototype._appendChild = HTMLElement.prototype.appendChild
    HTMLElement.prototype.appendChild = function(node) {
        if (!node.src || !node.src.includes('smil:gamer_ad/90426')) {
            return this._appendChild(node)
        }
    }
})();


function web() {
    hookAjax('/ajax/m3u8.php', async (_, opts) => {
        let src = (await get(opts.url)).src
        if (!src) {
            let s = getAd()[2]
            await fetch(`/ajax/videoCastcishu.php?s=${s}&sn=${animefun.videoSn}`)
            await showSkipping(config.skipTime)
            await fetch(`/ajax/videoCastcishu.php?s=${s}&sn=${animefun.videoSn}&ad=end`)
            src = (await get(opts.url)).src
        }
        if (!src) {
            toastr.error('請嘗試增加等待時間後重試', '跳過廣告失敗')
        } else {
            opts.success(JSON.stringify({ src }))
        }
    })
}


function mobile() {
    hookAjax('/ajax/m3u8.php', async (_, opts) => {
        let src = (await get(`https://api.gamer.com.tw/mobile_app/anime/v2/m3u8.php?sn=${animefun.videoSn}&device=${animefun.getdeviceid()}`)).src
        if (!src) {
            await get(`https://api.gamer.com.tw/mobile_app/anime/v1/stat_ad.php?sn=${animefun.videoSn}`)
            await showSkipping(config.skipTime)
            await get(`https://api.gamer.com.tw/mobile_app/anime/v1/stat_ad.php?sn=${animefun.videoSn}&ad=end`)
            src = (await get(`https://api.gamer.com.tw/mobile_app/anime/v2/m3u8.php?sn=${animefun.videoSn}&device=${animefun.getdeviceid()}`)).src
        }
        if (!src) {
            toastr.error('請切換為網頁版API後重試', '跳過廣告失敗')
        } else {
            opts.success(JSON.stringify({ src }))
        }
    })

    hookAjax('/ajax/elapse.php', async (ajax, opts) => {
        opts.success = () => {}
        return ajax(opts)
    })

    async function get(url) {
        const { response } = await GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            responseType: 'json',
            headers: {
                'User-Agent': 'Bahadroid (https://www.gamer.com.tw/)',
                'X-Bahamut-App-InstanceId': 'cIAk-QlPYbw',
                'X-Bahamut-App-Android': 'tw.com.gamer.android.animad',
                'X-Bahamut-App-Version': '167',
                'Host': 'api.gamer.com.tw'
            }
        })
        return response
    }
}


function setupMenu() {
    let ids = []

    registerMenu()

    GM.addValueChangeListener('useMobileAPI', () => registerMenu())

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

        if (config.useMobileAPI) {
            registerMenuCommand('使用網頁版API', () => {
                config.useMobileAPI = false
                swal.fire({
                    title: '修改成功',
                    icon: 'success',
                    timer: 800
                })
            }, 'M')
        } else {
            registerMenuCommand('使用手機版API', () => {
                config.useMobileAPI = true
                swal.fire({
                    title: '修改成功',
                    icon: 'success',
                    timer: 800
                })
            }, 'M')
        }

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

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


async function loadConfig() {
    window.config = new Proxy({
        skipTime: 25,
        useMobileAPI: true,
        version: '1.3.0'
    }, {
        get(target, key) {
            if (key in target) {
                return target[key]
            } else {
                throw 'Unknown Config'
            }
        },
        set(target, key, value) {
            if (key in target) {
                target[key] = value
                GM.setValue(key, value)
                return true
            } else {
                throw 'Unknown Config'
            }
        }
    })

    for (let [key, value] of Object.entries(config)) {
        let stored = await GM.getValue(key, null)
        config[key] = stored === null ? config[key] : stored
    }

    if (config.version != GM.info.script.version) {
        if (config.version == '1.3.0') {
            await GM.deleteValue('use experimental')
        } else if (config.version == '1.6.1') {
            await GM.deleteValue('duration')
        }
        config.version = GM.info.script.version
    }
}

async function showSkipping(duration) {
    let el = toastr.warning('', `正在跳過廣告... ${duration}(秒)`, { timeOut: duration * 1000, extendedTimeOut: 0, progressBar: true }).css('pointer-events', 'none')
    let title = el.find('.toast-title')
    while (duration > 0) {
        await sleep(1000)
        title.text(`正在跳過廣告... ${--duration}(秒)`)
    }
}

function hookAjax(match, hooker) {
    let ajax = $.ajax
    $.ajax = opts => {
        if (opts.url.includes(match)) {
            hooker(ajax, opts)
        } else {
            return ajax(opts)
        }
    }
}

async function get(url) {
    let res = await fetch(url)
    let json = await res.json()
    return json
}

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