YouTube视频&音乐广告拦截

拦截所有youtube视频广告,音乐播放广告,不留白,不闪屏,无感,体验第一。已适配移动端,支持自定义拦截,添加影视频道

目前为 2023-12-20 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube视频&音乐广告拦截
// @name:zh-CN   YouTube视频&音乐广告拦截
// @name:zh-TW   YouTube視頻&音樂廣告攔截 
// @name:zh-HK   YouTube視頻&音樂廣告攔截
// @name:zh-MO   YouTube視頻&音樂廣告攔截
// @name:en      YouTubeVideo&musicAdBlocking
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  拦截所有youtube视频广告,音乐播放广告,不留白,不闪屏,无感,体验第一。已适配移动端,支持自定义拦截,添加影视频道
// @description:zh-CN  拦截所有youtube视频广告,音乐播放广告,不留白,不闪屏,无感,体验第一。已适配移动端,支持自定义拦截,添加影视频道
// @description:zh-TW  攔截所有YouTube視頻廣告,音樂播放廣告,不留白,不閃屏,無感,體驗第一。已適配移動端,支持自定義攔截,添加影視頻道
// @description:zh-HK  攔截所有YouTube視頻廣告,音樂播放廣告,不留白,不閃屏,無感,體驗第一。已適配移動端,支持自定義攔截,添加影視頻道
// @description:zh-MO  攔截所有YouTube視頻廣告,音樂播放廣告,不留白,不閃屏,無感,體驗第一。已適配移動端,支持自定義攔截,添加影視頻道
// @description:en Intercept all YouTube video ads, music playback ads, without leaving blank space, no flash screens, seamless experience, the first choice in user experience. Adapted for mobile devices, supports customizable interception, and allows the addition of video channels
// @author       hua
// @match        https://www.youtube.com/*
// @match        https://m.youtube.com/*
// @match        https://music.youtube.com/watch*
// @connect      https://update.greasyfork.org/
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// @license      MIT
// ==/UserScript==



const open_config_keyword = '2333'
const display_error_keyword = '2444'

let open_recommend_shorts = GM_getValue("open_recommend_shorts", false);

let open_recommend_movie = GM_getValue("open_recommend_movie", false);

let open_recommend_popular = GM_getValue("open_recommend_popular", false);

let open_recommend_liveroom = GM_getValue("open_recommend_liveroom", false);

let open_recommend_tv_goodselect = false
// let open_recommend_liveroom = GM_getValue("open_recommend_liveroom", false);
// 'metadataBadgeRenderer.label........=- ~=精选' 桌面端主页 youtubeTv精选
let open_recommend_featured = true

let tmp_debugger_value

const script_url = 'https://update.greasyfork.org/scripts/480192/youtube%E5%B9%BF%E5%91%8A%E6%8B%A6%E6%88%AA.user.js'
let href = location.href
let home_page_ytInitialData_ad_rule
let watch_page_ytInitialData_ad_rule
let ytInitialPlayerResponse_ad_rule = [
    "abs:playerAds=-",
    "abs:adPlacements=-",
    "abs:adBreakHeartbeatParams=-",
    "abs:adSlots=-",
]
let playlist_Response_ad_rule = [
    "abs:[2].playerResponse.playerAds=-",
    "abs:[2].playerResponse.adPlacements=-",
    "abs:[2].playerResponse.adBreakHeartbeatParams=-",
    "abs:[2].playerResponse.adSlots=-",
]

let open_debugger = false
let isinint = false
let mobile_web
// "zh-CN" "en" "zh-TW"
let language
let movie_channel_info
let mobile_movie_channel_info
let flag_info

let debugger_ytInitialPlayerResponse
let debugger_ytInitialData
let page_type
let error_messages = []
const SPLIT_TAG = '###'
// const ignore_page_list = ['https://www.youtube.com/live_chat']

url_observer()
init()


function init() {
    log('初始化开始!', 0)
    let ytInitialPlayerResponse_value = unsafeWindow['ytInitialPlayerResponse']
    define_property_hook(unsafeWindow, 'ytInitialPlayerResponse', {
        get: function () {
            return ytInitialPlayerResponse_value
        },
        set: function (value) {
            if (value && open_debugger) debugger_ytInitialPlayerResponse = (typeof (value) === 'string') ? JSON.parse(value) : JSON.parse(JSON.stringify(value))
            let start_time = new Date().getTime()
            value && obj_process(value, ytInitialPlayerResponse_ad_rule, true)
            log('ytInitialPlayerResponse 时间:', new Date().getTime() - start_time, 'spend_time');
            ytInitialPlayerResponse_value = value
        },
        configurable: false
    });

    let ytInitialData_value = unsafeWindow['ytInitialData']
    define_property_hook(unsafeWindow, 'ytInitialData', {
        get: function () {
            return ytInitialData_value
        },
        set: function (value) {
            let start_time = new Date().getTime()
            let y_type = typeof (value)
            if (open_debugger && value !== undefined && value !== null) debugger_ytInitialData = (y_type === 'string') ? JSON.parse(value) : JSON.parse(JSON.stringify(value))
            if (value && y_type === 'string') {
                value = JSON.parse(value)
            }
            if (/watch/.test(href)) {
                value && obj_process(value, watch_page_ytInitialData_ad_rule, true)
            } else {
                value && obj_process(value, home_page_ytInitialData_ad_rule, true)
            }
            if (y_type === 'string') value = JSON.stringify(value)
            ytInitialData_value = value
            log('ytInitialData 时间:', new Date().getTime() - start_time, 'spend_time')
        },
        configurable: false
    })

    define_property_hook(navigator, 'userAgent', {
        get: function () {
            return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
        }
    })

    let origin_creatElement = document.createElement
    document.createElement.toString = origin_creatElement.toString
    document.createElement = function () {
        let node = origin_creatElement.apply(this, arguments)
        if (arguments[0] === 'template') {
            let innerhtml_getter = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").get;
            let innerhtml_setter = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set;
            define_property_hook(node, 'innerHTML', {
                get: function () {
                    return innerhtml_getter.call(node)
                },
                set: function (value) {
                    // if (value.toString().indexOf('ytd-continuation-item-renderer')>-1){
                    //     if (href.indexOf('https://www.youtube.com/watch')>-1){
                    //         value = ''
                    //         log(value);
                    //         log('弹窗去掉------->ytd-continuation-item-renderer');
                    // }
                    if (value.toString().indexOf('yt-mealbar-promo-renderer') > -1) {
                        log('弹窗去掉------->yt-mealbar-promo-renderer', 'node_process');
                        value = ''
                    }
                    innerhtml_setter.call(node, value)
                }
            })
        }
        return node
    }

    async function deal_resposn(name, response, rule) {
        const responseClone = response.clone();
        let result = await responseClone.text()
        let start_time = new Date().getTime()
        result = text_process(result, rule, 'insert', true)
        log(name + ' 时间:', new Date().getTime() - start_time, 'spend_time');
        return new Response(result, response)
    }
    const origin_fetch = unsafeWindow.fetch;
    if (origin_fetch.toString() !== 'function fetch() { [native code] }') {
        log('fetch have been modified', 'error')
    }
    unsafeWindow.fetch = function () {
        const fetch_ = async function (uri, options) {
            async function fetch_request(response) {
                let url = response.url
                return_response = response
                if (url.indexOf('youtubei/v1/next') > -1) {
                    return await deal_resposn('next', response, watch_page_ytInitialData_ad_rule)
                }
                if (url.indexOf('youtubei/v1/player') > -1) {
                    return await deal_resposn('player', response, ytInitialPlayerResponse_ad_rule)
                }
                if (url.indexOf('youtubei/v1/browse') > -1) {
                    let rule = home_page_ytInitialData_ad_rule
                    if (page_type === 'home' && !open_recommend_liveroom) {
                        let node, category_text
                        if (mobile_web) {
                            node = document.querySelector('#filter-chip-bar > div > ytm-chip-cloud-chip-renderer.selected')
                            category_text = node && node.textContent
                        } else {
                            node = document.querySelector('#chips > yt-chip-cloud-chip-renderer.style-scope.ytd-feed-filter-chip-bar-renderer.iron-selected')
                            category_text = node && node.querySelector('#text').textContent
                        }
                        const filter_list = [flag_info.category_game, flag_info.category_live, flag_info.category_news]
                        if (filter_list.includes(category_text)) {
                            rule = home_page_ytInitialData_ad_rule.filter(item => item.indexOf(flag_info.live) === -1)
                        }
                    }
                    return await deal_resposn('browse', response, rule)
                }
                if (url.indexOf('https://m.youtube.com/youtubei/v1/guide') > -1) {
                    return await deal_resposn('guide', response, home_page_ytInitialData_ad_rule)
                }
                if (url.indexOf('/youtubei/v1/search') > -1) {
                    return await deal_resposn('guide', response, home_page_ytInitialData_ad_rule)
                }
                return return_response
            }
            return origin_fetch(uri, options).then(fetch_request)
        }
        const names = Object.getOwnPropertyNames(origin_fetch);
        for (let i = 0; i < names.length; i++) {
            if (names[i] in fetch_)
                continue;
            let desc = Object.getOwnPropertyDescriptor(origin_fetch, names[i])
            define_property_hook(fetch_, names[i], desc);
        }
        return fetch_
    }()

    const origin_open = XMLHttpRequest.prototype.open;
    if (origin_open.toString() !== 'function open() { [native code] }') {
        log('XMLHttpRequest open have been modified', 'error')
    }
    const xhr_response_getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "response").get;
    XMLHttpRequest.prototype.open = function () {
        const xhr = this;
        const xhr_rsponse_hander = {
            get: () => {
                let result = xhr_response_getter.call(xhr);
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    if (xhr.responseURL.indexOf('youtubei/v1/player') > -1) {
                        result = text_process(result, ytInitialPlayerResponse_ad_rule, 'insert', true)
                    }
                    if (xhr.responseURL.indexOf('youtube.com/playlist') > -1) {
                        let obj
                        try {
                            obj = JSON.parse(result)
                        } catch (error) {
                            log('JSON解析失败', 1);
                            return result
                        }
                        obj_process(obj[2].playerResponse, ytInitialPlayerResponse_ad_rule, true)
                        obj_process(obj[3].response, watch_page_ytInitialData_ad_rule, true)
                        tmp_debugger_value = obj
                        result = JSON.stringify(obj)
                    }
                } else {
                    result = ''
                }
                return result
            },
            configurable: true
        }
        define_property_hook(xhr, "responseText", xhr_rsponse_hander);
        define_property_hook(xhr, "response", xhr_rsponse_hander);
        origin_open.apply(this, arguments);
    }

    document.addEventListener('DOMContentLoaded', function () {
        !mobile_web && search_listener()
        checke_update()
    })

    if (unsafeWindow.ytcfg) {
        if (unsafeWindow.ytcfg.data_ && unsafeWindow.ytcfg.data_.HL) config_init()
        !(unsafeWindow.ytcfg.data_ && unsafeWindow.ytcfg.data_.HL) && define_property_hook(unsafeWindow.ytcfg, 'msgs', {
            get: function () {
                return this._msgs
            },
            set: function (newValue) {
                if (newValue.__lang__) config_init()
                this._msgs = newValue;
            }
        })
    } else {
        define_property_hook(unsafeWindow, 'ytcfg', {
            get: function () {
                return this._ytcfg
            },
            set: function (newValue) {
                if (newValue.set) {
                    const orgin_set = newValue.set
                    newValue.set = function () {
                        orgin_set.apply(this, arguments)
                        if (arguments[0].HL) {
                            config_init(arguments[0].HL)
                        }
                    }
                }
                this._ytcfg = newValue;
            }
        });
    }

    isinint = true
    log('初始化结束!', 0)
}

function filter_page() {
    let base_url = href.split('?')[0]
    let urls = ['youtube.com/watch', 'youtube.com/', 'youtube.com', 'youtube.com/playlist']
    for (let [index, check_url] of urls.entries()) {
        if (new RegExp(check_url + '$').test(base_url)) {
            page_type = (index == 1 || index == 2) ? 'home' : 'watch'
            return false
        }
    }
    log(`过滤页面:${href}`, 0)
    return true
}

function native_method_hook(method_str, handler) {
    if (eval(`typeof(${method_str}) !== 'function'`)) {
        console.log(method_str, 'have been modified', 'error')
        return
    }
    const method_name = eval(`${method_str}.name`)
    if (eval(`${method_str}.toString() !== 'function ${method_name}() { [native code] }'`)) {
        console.log(method_str, 'have been modified!', 'error')
    }
    handler = handler(eval(method_str))
    eval(`${method_str}=handler`)
}

function define_property_hook(obj, property, descriptor) {
    Object.defineProperty(obj, property, descriptor)
    if (descriptor.get) {
        const get_ = Object.getOwnPropertyDescriptor(obj, property).get
        if (descriptor.get !== get_) {
            log("hook " + property + " failed!", 'error')
            return
        }
    }
    if (descriptor.set) {
        const set_ = Object.getOwnPropertyDescriptor(obj, property).set
        if (descriptor.set !== set_)
            log("hook " + property + " failed!", 'error')
    }
}

function config_init(tmp_language = null) {
    home_page_ytInitialData_ad_rule = undefined
    watch_page_ytInitialData_ad_rule = undefined

    if (isinint) {
        setTimeout(search_listener, 500)
    }
    if (filter_page()) return
    if (!tmp_language)
        language = unsafeWindow['ytcfg'].msgs ? unsafeWindow['ytcfg'].msgs.__lang__ : unsafeWindow['ytcfg'].data_.HL
    else
        language = tmp_language
    let flag_infos = {
        "zh-CN": {
            "sponsored": "赞助商",
            "free_movie": "免费(含广告)",
            "live": "直播",
            "movie_channel": "影视",
            "free_primetime_movie": "免费 Primetime 电影",
            "think_video": "你对这个视频有何看法?|此推荐内容怎么样?",
            "try": "试用",
            "recommend_popular": "时下流行",
            "featured": "Featured",
            "category_live": "直播",
            "category_game": "游戏",
            "category_news": "新闻",
            "btn_recommend_movie": "电影推荐",
            "btn_recommend_shorts": "Shorts推荐",
            "btn_recommend_liveroom": "直播推荐",
            "btn_recommend_popular": "时下流行",
            "btn_save": "保存",
            "goodselect": "精选"

        },
        "zh-TW": {
            "sponsored": "赞助商",
            "free_movie": "免費 \\(含廣告\\)",
            "live": "直播",
            "movie_channel": "電影與電視節目",
            "free_primetime_movie": "免費的特選電影",
            "think_video": "你对这个视频有何看法?|此推荐内容怎么样?",
            "try": "試用",
            "recommend_popular": "時下流行",
            "featured": "Featured",
            "category_live": "直播中",
            "category_game": "遊戲",
            "category_news": "新聞",
            "btn_recommend_movie": "电影推薦",
            "btn_recommend_shorts": "Shorts推薦",
            "btn_recommend_liveroom": "直播推薦",
            "btn_recommend_popular": "時下流行",
            "btn_save": "保存",
            "goodselect": "精選內容"
        },
        "en": {
            "sponsored": "Sponsored",
            "free_movie": "Free with ads",
            "live": "LIVE",
            "movie_channel": "Movies & TV",
            "free_primetime_movie": "Free Primetime movies",
            "think_video": "What did you think of this video?|此推荐内容怎么样?",
            "try": "试用",
            "recommend_popular": "Trending",
            "featured": "Featured",
            "category_live": "Live",
            "category_game": "Gaming",
            "category_news": "News",
            "btn_recommend_movie": "MovieRecommend",
            "btn_recommend_shorts": "ShortsRecommend",
            "btn_recommend_liveroom": "LiveRecommend",
            "btn_recommend_popular": "TrendingRecommend",
            "btn_save": "Save",
            "goodselect": "精选"
        }
    }

    flag_info = flag_infos[language]
    movie_channel_info = {
        "guideEntryRenderer": {
            "navigationEndpoint": {
                "clickTrackingParams": "CBQQnOQDGAIiEwj5l8SLqPiCAxUXSEwIHbf1Dw0=",
                "commandMetadata": {
                    "webCommandMetadata": {
                        "url": "/feed/storefront",
                        "webPageType": "WEB_PAGE_TYPE_BROWSE",
                        "rootVe": 6827,
                        "apiUrl": "/youtubei/v1/browse"
                    }
                },
                "browseEndpoint": {
                    "browseId": "FEstorefront"
                }
            },
            "icon": {
                "iconType": "CLAPPERBOARD"
            },
            "trackingParams": "CBQQnOQDGAIiEwj5l8SLqPiCAxUXSEwIHbf1Dw0=",
            "formattedTitle": {
                "simpleText": flag_info.movie_channel
            },
            "accessibility": {
                "accessibilityData": {
                    "label": flag_info.movie_channel
                }
            }
        }
    }
    mobile_movie_channel_info = {
        "navigationItemViewModel": {
            "text": {
                "content": flag_info.movie_channel
            },
            "icon": {
                "sources": [
                    {
                        "clientResource": {
                            "imageName": "CLAPPERBOARD"
                        }
                    }
                ]
            },
            "onTap": {
                "parallelCommand": {
                    "commands": [
                        {
                            "innertubeCommand": {
                                "clickTrackingParams": "CBQQnOQDGAIiEwj5l8SLqPiCAxUXSEwIHbf1Dw0=",
                                "hideMoreDrawerCommand": {}
                            }
                        },
                        {
                            "innertubeCommand": {
                                "clickTrackingParams": "CBQQnOQDGAIiEwj5l8SLqPiCAxUXSEwIHbf1Dw0=",
                                "commandMetadata": {
                                    "webCommandMetadata": {
                                        "url": "/feed/storefront",
                                        "webPageType": "WEB_PAGE_TYPE_CHANNEL",
                                        "rootVe": 3611,
                                        "apiUrl": "/youtubei/v1/browse"
                                    }
                                },
                                "browseEndpoint": {
                                    "browseId": "FEstorefront"
                                }
                            }
                        }
                    ]
                }
            },
            "loggingDirectives": {
                "trackingParams": "CBQQnOQDGAIiEwj5l8SLqPiCAxUXSEwIHbf1Dw0=",
                "visibility": {
                    "types": "12"
                },
                "enableDisplayloggerExperiment": true
            }
        }
    }

    let column_recommend_rule
    let item_label_fifter_rule
    let watch_page_item_label_fifter_rule
    let home_page_item_label_fifter_rule
    let add_movie_channel_rule
    mobile_web = href.indexOf('https://m.youtube.com/') > -1
    watch_page_ytInitialData_ad_rule = home_page_ytInitialData_ad_rule = null
    //打开直播频道
    let open_live_channel = href.indexOf('channel/UC4R8DWoMoI7CAwX8_LjQHig') > -1
    let open_movie_channel = href.indexOf('feed/storefront') > -1
    if (mobile_web) {
        column_recommend_rule = 'reelShelfRenderer.title.runs[0].text......=- ~='
        mobile_web_extra_column_recommend_rule = 'pivotBarItemRenderer.title.runs[0].text.....=- ~='
        // 直播规则
        let ad_label = 'metadataBadgeRenderer.label.....=- ~=' + flag_info.sponsored
        if (!open_recommend_movie && !open_movie_channel) ad_label += '|' + flag_info.free_movie
        item_label_fifter_rule = [ad_label]
        home_page_item_label_fifter_rule = watch_page_item_label_fifter_rule = item_label_fifter_rule
        if (!open_recommend_liveroom && !open_live_channel) {
            item_label_fifter_rule.push('text.accessibility.accessibilityData.label........=- ~=' + flag_info.live)
            home_page_item_label_fifter_rule = item_label_fifter_rule
            watch_page_item_label_fifter_rule = item_label_fifter_rule
        }
        add_movie_channel_rule = "loadingStrategy.inlineContent.moreDrawerViewModel.content=+obj(mobile_movie_channel_info) !~=" + flag_info.movie_channel
    } else {
        column_recommend_rule = 'richShelfRenderer.title.runs[0].text......=- ~='
        let ad_label
        if (href.indexOf('watch') > -1) {
            ad_label = 'metadataBadgeRenderer.label.....=- ~=' + flag_info.sponsored
        } else {
            ad_label = 'metadataBadgeRenderer.label......=- ~=' + flag_info.sponsored
        }
        if (!open_recommend_movie && !open_movie_channel) ad_label += '|' + flag_info.free_movie
        item_label_fifter_rule = [ad_label]
        home_page_item_label_fifter_rule = watch_page_item_label_fifter_rule = item_label_fifter_rule
        if (!open_recommend_liveroom && !open_live_channel) {
            item_label_fifter_rule.push('text.accessibility.accessibilityData.label........=- ~=' + flag_info.live)
            watch_page_item_label_fifter_rule = item_label_fifter_rule.concat(['metadataBadgeRenderer.label.....=- ~=' + flag_info.live])
            home_page_item_label_fifter_rule = item_label_fifter_rule.concat(['metadataBadgeRenderer.label......=- ~=' + flag_info.live])
        }
        add_movie_channel_rule = "loadingStrategy.inlineContent.moreDrawerViewModel.content=+obj(movie_channel_info) !~=" + flag_info.movie_channel
    }

    if (!open_recommend_tv_goodselect) {
        if (home_page_item_label_fifter_rule) {
            home_page_item_label_fifter_rule.push('metadataBadgeRenderer.label.......=- ~=' + flag_info.goodselect)
        } else {
            home_page_item_label_fifter_rule = ['metadataBadgeRenderer.label.......=- ~=' + flag_info.goodselect]
        }
    }

    let column_recommend_list = []
    if (!open_recommend_shorts) column_recommend_list.push('Shorts')
    if (!open_recommend_movie && !open_movie_channel) column_recommend_list.push(flag_info.free_primetime_movie)
    if (!open_recommend_popular) column_recommend_list.push(flag_info.recommend_popular)
    if (column_recommend_list.length > 0) {
        column_recommend_rule += column_recommend_list.join('|')
        if (mobile_web) mobile_web_extra_column_recommend_rule += column_recommend_list.join('|')
    }

    home_page_ytInitialData_ad_rule = [
        'title.runs[0].text......=- ~=YouTube Premium|' + flag_info.think_video,
        'richGridRenderer.masthead=-',
        'videoOwnerRenderer=- /.purchaseButton.buttonRenderer.text.simpleText~=' + flag_info.try,
        'richItemRenderer.content.adSlotRenderer..=-',
        add_movie_channel_rule
    ]
    if (home_page_item_label_fifter_rule) home_page_ytInitialData_ad_rule = home_page_ytInitialData_ad_rule.concat(home_page_item_label_fifter_rule)

    if (open_recommend_featured) {
        home_page_ytInitialData_ad_rule.push('brandVideoSingletonRenderer.badgeText.runs[0].text.......=- ~=' + flag_info.featured)
        home_page_ytInitialData_ad_rule.push('brandVideoShelfRenderer.badgeText.runs[0].text.......=- ~=' + flag_info.featured)
    }

    if (!open_recommend_shorts || (!open_recommend_movie && !open_movie_channel) || !open_recommend_popular) {
        home_page_ytInitialData_ad_rule.push(column_recommend_rule)
        if (mobile_web) {
            home_page_ytInitialData_ad_rule.push(mobile_web_extra_column_recommend_rule)
        }
    }

    watch_page_ytInitialData_ad_rule = [
        'tvfilmOfferModuleRenderer=- /.masthead$exist',
        'merchandiseShelfRenderer=-',
        'adSlotRenderer.=-'
    ]
    if (watch_page_item_label_fifter_rule) watch_page_ytInitialData_ad_rule = watch_page_ytInitialData_ad_rule.concat(watch_page_item_label_fifter_rule)

}

function search_listener() {
    const search_selector = href.indexOf('https://m.youtube.com/') > -1 ? 'input.searchbox-input.title' : 'input[id="search"]'
    const search_input_node = document.querySelector(search_selector)
    if (search_input_node) {
        search_input_node.oninput = function (event) {
            if (open_config_keyword === this.value || this.value === display_error_keyword) {
                setTimeout(function () {
                    if (search_input_node.value === open_config_keyword) {
                        display_config_win()
                    }
                    if (search_input_node.value === display_error_keyword) {
                        alert(`error messages(init ${isinint ? 'success' : 'failed'})\n----------------\n${error_messages.join('\n')}`)
                        if (error_messages.length > 0) {
                            copyToClipboard(error_messages.join('\n'))
                        }
                    }
                }, 500)
            }
        };
    }
}

function copyToClipboard(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text)
    var textarea = document.createElement("textarea")
    textarea.value = text;
    document.body.appendChild(textarea);
    textarea.select();
    document.execCommand('copy');
    document.body.removeChild(textarea)
}


function url_observer() {
    if (unsafeWindow.navigation) {
        unsafeWindow.navigation.addEventListener('navigate', (event) => {
            url_change(event)
        })
        return
    }
    const _historyWrap = function (type) {
        const orig = unsafeWindow.history[type];
        const e = new Event(type);
        return function () {
            const rv = orig.apply(this, arguments);
            e.arguments = arguments;
            unsafeWindow.dispatchEvent(e);
            return rv;
        };
    };
    unsafeWindow.history.pushState = _historyWrap('pushState');
    unsafeWindow.history.replaceState = _historyWrap('replaceState');
    unsafeWindow.addEventListener('replaceState', function (event) {
        url_change(event)
    })
    unsafeWindow.addEventListener('pushState', function (event) {
        url_change(event)
    });
    unsafeWindow.addEventListener('popstate', function (event) {
        url_change(event)
    })
    unsafeWindow.addEventListener('hashchange', function (event) {
        url_change(event)
    })

}

function url_change(event = null) {
    if (event && event.destination.url.indexOf('about:blank') === 0) return
    href = event ? event.destination.url : location.href
    log('网页url改变 href -> ' + href, 0)
    config_init()
}

let debugger_config_info = {
    'ytInitialPlayerResponse': debugger_ytInitialPlayerResponse,
    'ytInitialData': debugger_ytInitialData,
    'info': [
        'home_page_ytInitialData_ad_rule',
        'watch_page_ytInitialData_ad_rule',
        'ytInitialPlayerResponse_ad_rule',
        'open_recommend_liveroom',
        'open_recommend_popular',
        'open_recommend_movie',
        'open_recommend_shorts',
        'language',
        'mobile_web',
        'tmp_debugger_value'
    ]
}

unsafeWindow.debugger_ = function (action = null) {
    let keys = Object.keys(debugger_config_info)
    if (!action && action !== 0) { debugger; return }
    if (action === 'ytInitialPlayerResponse') return console.log('ytInitialPlayerResponse', debugger_ytInitialPlayerResponse);
    if (action === 'ytInitialData') return console.log('ytInitialData', debugger_ytInitialData);
    if (action === 'info') {
        for (let key of debugger_config_info['info']) {
            console.log(key, eval(key));
        }
    }
    if (action === 'list') {
        keys.forEach(function (key, index) {
            console.log(index, key);
        })
    }
    if (typeof (action) === 'number' && action < keys.length) {
        unsafeWindow.debugger_(keys[action])
    }
}

function log() {
    let arguments_arr = [...arguments]
    let flag = arguments_arr.pop()
    if (flag === 'error') {
        error_messages.push(arguments_arr.join(' '))
    }
    if (flag !== 0) arguments_arr.push(getCodeLocation())
    if (flag === 0 || open_debugger) console.log(...arguments_arr);
}

function getCodeLocation() {
    const callstack = new Error().stack.split("\n");
    callstack.shift();
    while (callstack.length && callstack[0].indexOf("chrome-extension://") !== -1) {
        callstack.shift();
    }
    if (!callstack.length) {
        return "";
    }
    return '\n' + callstack[0].trim();
}

function display_config_win() {
    const css_str = '#set_list { z-index:9999999999; display: flex; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 20px; background-color: #fff; border: 1px solid #ccc; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 10px; } #set_button { margin: 0 10px; display: inline-block; padding: 5px 10px; background-color: #3498db; color: #fff; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; } #set_button:hover { background-color: #2980b9; }'
    const style = document.createElement("style");
    style.innerText = css_str;
    document.querySelector('body').appendChild(style)
    const config_info = {
        "open_recommend_movie": flag_info.btn_recommend_movie,
        "open_recommend_shorts": flag_info.btn_recommend_shorts,
        "open_recommend_liveroom": flag_info.btn_recommend_liveroom,
        "open_recommend_popular": flag_info.btn_recommend_popular,
    }
    const container = document.createElement("div");
    container.id = "set_list"
    for (let key in config_info) {
        let label = document.createElement("label")
        let input = document.createElement("input")
        input.id = key
        input.type = 'checkbox'
        input.checked = eval(key)
        label.appendChild(input)
        let span = document.createElement("span")
        span.textContent = config_info[key]
        span.style.userSelect = 'none'
        label.appendChild(span)
        container.appendChild(label)
    }
    let button = document.createElement("button")
    button.id = "set_button"
    button.textContent = flag_info.btn_save
    button.onclick = function () {
        for (let key in config_info) {
            GM_setValue(key, document.querySelector('#' + key).checked);
            eval(key + '=' + document.querySelector('#' + key).checked)
        }
        document.querySelector('body').removeChild(container)
    }
    container.appendChild(button)
    document.querySelector('body').appendChild(container)
    let search_list_node = document.querySelector('body > div.gstl_50.sbdd_a')
    if (search_list_node) {
        search_list_node.style.display = 'none'
    }
}

function display_update_win() {
    function btn_click() {
        btn = this
        if (btn.id === 'go_btn') {
            location.href = script_url
        }
        document.querySelector('body').removeChild(container)
    }
    const css_str = "#update_tips_win { z-index:9999999999; display: flex; position: fixed; bottom: 20px; right: 20px; padding: 10px 20px; background-color: #fff; border: 1px solid #ccc; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 10px; } .btn { margin: 0 10px; display: inline-block; padding: 5px 10px; background-color: #3498db; color: #fff; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; } .btn:hover { background-color: #2980b9; }";
    const style = document.createElement("style");
    style.innerText = css_str;
    document.querySelector('body').appendChild(style)
    const container = document.createElement("div")
    container.id = "update_tips_win"
    const span = document.createElement("span")
    span.textContent = GM_info.script.name + '有更新了!!'
    container.appendChild(span)
    const go_btn = document.createElement("button")
    go_btn.textContent = 'GO'
    go_btn.id = 'go_btn'
    go_btn.className = 'btn'
    go_btn.onclick = btn_click
    container.appendChild(go_btn)
    const no_btn = document.createElement("button")
    no_btn.textContent = 'NO'
    no_btn.className = 'btn'
    no_btn.id = 'no_btn'
    no_btn.onclick = btn_click
    container.appendChild(no_btn)
    document.querySelector('body').appendChild(container)
}

function checke_update() {
    let last_check_time = GM_getValue('last_check_time', 0)
    if ((new Date().getTime() - last_check_time) < 1000 * 60 * 60 * 24) return
    GM_xmlhttpRequest({
        method: 'GET',
        url: script_url,
        onload: function (response) {
            const onlineScript = response.responseText;
            // 从线上脚本中提取版本号和元数据信息
            const onlineMeta = onlineScript.match(/@version\s+([^\s]+)/i);
            const onlineVersion = onlineMeta ? onlineMeta[1] : '';
            if (onlineVersion > GM_info.script.version) {
                display_update_win()
            }
        }
    });
    GM_setValue('last_check_time', new Date().getTime())
}

function text_process(data, values, mode, traverse_all) {
    mode = mode || 'cover'
    if (mode === 'reg') {
        for (let value of values) {
            let patten_express = value.split(SPLIT_TAG)[0]
            let replace_value = value.split(SPLIT_TAG)[1]
            let patten = new RegExp(patten_express, "g")
            data = data.replace(patten, replace_value)
        }
    }
    if (mode === 'cover') {
        data = values[0]
    }
    if (mode === 'insert') {
        traverse_all = traverse_all || false
        let json_data
        try {
            json_data = JSON.parse(data)
        } catch (error) {
            log('text_process JSON parse error', 0)
            return data
        }
        obj_process(json_data, values, traverse_all)
        data = JSON.stringify(json_data)
    }
    return data
}

function obj_process(json_obj, express_list, traverse_all = false) {
    let abs_path_info_list = []
    let relative_path_info_list = []
    let relative_path_list = []
    let relative_short_path_list = []
    if (!json_obj || !express_list) return
    const is_array_obj = Array.isArray(json_obj)
    function add_data_to_abs_path(path, relative_path, operator, value, condition, array_index, path_extral) {
        let tmp
        path = path.replace(/\.[\d\w\-\_\$@]+/g, function (match) {
            return '["' + match.slice(1) + '"]'
        })
        if (array_index !== "*") {
            tmp = {}
            path = path + (array_index ? '[' + array_index + ']' : '')
            tmp.path = path
            tmp.relative_path = relative_path
            tmp.operator = operator
            tmp.value = value
            tmp.condition = condition
            tmp.path_extral = path_extral
            abs_path_info_list.push(tmp)
            return
        }
        let array_length
        try {
            array_length = eval(path + '.length')
            if (!array_length) return
        } catch (error) {
            return
        }
        for (let tmp_index = array_length - 1; tmp_index >= 0; tmp_index--) {
            tmp = {}
            tmp.path = path + "[" + tmp_index + "]"
            tmp.operator = operator
            tmp.value = value
            tmp.condition = condition
            tmp.path_extral = path_extral
            tmp.relative_path = relative_path
            abs_path_info_list.push(tmp)
        }
    }

    function value_parase(parase_value) {
        const json_math = parase_value.match(/^json\((.*)\)$/)
        if (json_math) return JSON.parse(json_math[1])
        const obj_match = parase_value.match(/^obj\((.*)\)$/)
        if (obj_match) return eval(obj_match[1])
        const number_match = parase_value.match(/^num\((.*)\)$/)
        if (number_match) return eval(number_match[1])
        if (parase_value === 'undefined') return undefined
        if (parase_value === 'null') return null
        return parase_value
    }

    express_list.forEach(express => {
        let reg
        let express_type = typeof (express)
        let matchs
        let conditions
        let value
        reg = /^(abs:)?([a-zA-Z_0-9\.\*\[\]]*)((=\-|~=|=\+|=))(.*)?/
        if (express_type === 'string') {
            matchs = express.match(reg)
        } else {
            matchs = express.value.match(reg)
            conditions = express.conditions
        }
        let abs = matchs[1]
        let path = matchs[2]
        let path_extral_match = path.match(/\/?\.+$/)
        let path_extral
        if (path_extral_match) {
            path_extral = {}
            let len
            if (path_extral_match[0].indexOf('/') === 0) {
                len = path_extral_match[0].length - 1
                path_extral['child'] = len
            } else {
                len = path_extral_match[0].length
                path_extral['parent'] = len
            }
            path = path.slice(0, path.length - len)
        }
        let operator = matchs[3]
        if (express_type === 'string') {
            let tmp_value = matchs[5] || ''
            let split_index = tmp_value.indexOf(' ')
            if (split_index > -1) {
                value = tmp_value.substring(0, split_index)
                conditions = tmp_value.substring(split_index + 1)
                conditions = {
                    'value': [conditions]
                }
            } else {
                value = tmp_value
            }
        }
        matchs = path.match(/\[(\*?\d*)\]$/)
        let array_index
        if (matchs) {
            path = path.replace(/\[(\*?\d*)\]$/, '')
            array_index = matchs[1]
        }
        if (abs) {
            add_data_to_abs_path(`json_obj${is_array_obj ? '' : '.'}` + path, path, operator, value, conditions, array_index, path_extral)
        } else {
            relative_path_list.push(path)
            let tmp_short_path = path.split('.').pop()
            relative_short_path_list.push(tmp_short_path)
            relative_path_info_list.push({
                "path": path,
                "operator": operator,
                "value": value,
                "conditions": conditions,
                "array_index": array_index,
                "path_extral": path_extral
            })
        }
    })
    if (relative_path_list.length > 0) {
        let dec_list = []
        let dec_index_list = []
        obj_property_traverse(json_obj, '', {
            "short_keys": relative_short_path_list,
            "real_keys": relative_path_list
        }, dec_list, dec_index_list, traverse_all)
        for (let i = 0; i < dec_index_list.length; i++) {
            let real_index = dec_index_list[i]
            let real_path_info = relative_path_info_list[real_index]
            let tmp_path = 'json_obj' + dec_list[i]
            add_data_to_abs_path(tmp_path, real_path_info.path, real_path_info.operator, real_path_info.value, real_path_info.conditions, real_path_info.array_index, real_path_info.path_extral)
        }
    }
    abs_path_info_list.sort((a, b) => a.path > b.path ? 1 : -1)
    for (let path_info of abs_path_info_list) {
        if (!obj_conditional(path_info, json_obj)) continue
        let operator = path_info.operator
        let path = path_info.path
        let value = path_info.value
        let path_extral = path_info.path_extral
        if (path_extral) {
            let positions = []
            let regex = /\]/g
            let match
            while ((match = regex.exec(path)) !== null) {
                positions.push(match.index);
            }
            if (positions.length === 0) continue
            if ('parent' in path_extral) {
                if (positions.length - path_extral['parent'] - 1 < 0) continue
                split_index = positions[positions.length - path_extral['parent'] - 1] + 1
                path = path.slice(0, split_index)
            }
        }
        try {
            if (operator === '=-') {
                let math = path.match(/(.*)\[(\d+)\]$/)
                if (math) {
                    let arr_express = math[1]
                    let index = math[2]
                    eval(arr_express + '.splice(' + index + ',1)')
                    log(`依据:${path_info.relative_path}${!path_info.path_extral ? '' : JSON.stringify(path_info.path_extral)}${path_info.operator} ${!path_info.condition ? '' : JSON.stringify(path_info.condition)}`, 'obj_process');
                    log('删除属性-->' + arr_express + '[' + index + ']', 'obj_process');
                } else {
                    eval('delete ' + path)
                    log(`依据:${path_info.relative_path}${!path_info.path_extral ? '' : JSON.stringify(path_info.path_extral)}${path_info.operator} ${!path_info.condition ? '' : JSON.stringify(path_info.condition)}`, 'obj_process');
                    log('删除属性-->' + path, 'obj_process');
                }
            }
            if (operator === '=+') {
                let type_ = eval('typeof (' + path + ')')
                if (type_ === 'object' && eval('Array.isArray(' + path + ')')) type_ = 'array'
                value = value_parase(value)
                if (type_ === 'array') eval(path + '.push(value)')
                if (type_ === 'string' || type_ === 'number') eval(path + '= ' + path + '+ value')
            }
            if (operator === '~=') {
                let search_value = value.split(SPLIT_TAG)[0]
                let replace_value = value.split(SPLIT_TAG)[1]
                eval(path + '=' + path + '.replace(new RegExp(search_value, "g"), replace_value)')

            }
            if (operator === '=') {
                value = value_parase(value)
                eval(path) !== undefined && eval(path + '=value')
                log(`依据:${path_info.relative_path}${!path_info.path_extral ? '' : JSON.stringify(path_info.path_extral)}${path_info.operator} ${!path_info.condition ? '' : JSON.stringify(path_info.condition)}`, 'obj_process');
                log('修改属性-->' + path, 'obj_process');
            }
        } catch (error) {
            log(path, operator + '错误', 'obj_process');
        }
    }

}

function obj_conditional(express_info, json_obj) {
    //json_obj 在eval里直接调用
    if (!express_info['condition']) return true
    let condition_infos = express_info['condition']
    // 与 
    for (let condition_list of Object.values(condition_infos)) {
        let result = false
        for (let condition of condition_list) {
            let reg = /^([a-zA-Z_0-9\/\.\[\]]*)?(.*)/
            let match = condition.match(reg)
            let condition_path = match[1]
            let mod
            if (condition_path) {
                if (condition_path.indexOf('/') === 0) {
                    mod = 'child'
                } else if (condition_path.indexOf('.') === 0) {
                    mod = 'parent'
                } else {
                    mod = 'other'
                }
            } else {
                condition_path = express_info.path
            }
            let conditional_express = match[2]
            if (mod === 'child') {
                condition_path = express_info.path + condition_path.slice(1).replace(/\.[\d\w\-\_\$@]+/g, function (match) {
                    return '["' + match.slice(1) + '"]'
                })
            }
            if (mod === 'parent') {
                let reg = /^\.+/
                let matchs = condition_path.match(reg)
                let positions = []
                let regex = /\]/g
                while ((match = regex.exec(express_info.path)) !== null) {
                    positions.push(match.index);
                }
                if (positions.length > 0) {
                    let split_index = positions[positions.length - matchs[0].length - 1] + 1
                    let short_condition_path = condition_path.replace(reg, '')
                    if (!/^\[/.test(short_condition_path)) {
                        short_condition_path = '.' + short_condition_path
                    }
                    condition_path = express_info.path.slice(0, split_index) + short_condition_path.replace(/\.[\d\w\-\_\$@]+/g, function (match) {
                        return '["' + match.slice(1) + '"]'
                    })
                }
            }
            if (mod === 'other') {
                condition_path = (`json_obj${is_array_obj ? '' : '.'}` + condition_path).replace(/\.[\d\w\-\_\$@]+/g, function (match) {
                    return '["' + match.slice(1) + '"]'
                })
            }
            let condition_value
            try {
                condition_value = eval(condition_path)
            } catch (error) {
                continue
            }
            result = value_conditional(condition_value, conditional_express)
            result && log('条件成立-->', condition_value, 'obj_process');

            if (result) break
        }
        if (!result) return false
    }
    return true
}

function obj_property_traverse(obj, cur_path, dec_infos, dec_list, dec_index_list, traverse_all = false) {
    if (Array.isArray(obj)) {
        obj.forEach((tmp_obj, index) => {
            let tmp_path = cur_path + '[' + index + ']'
            if (!tmp_obj || typeof (tmp_obj) !== 'object') return
            obj_property_traverse(tmp_obj, tmp_path, dec_infos, dec_list, dec_index_list, traverse_all)
        })
        return
    }
    Object.keys(obj).forEach((key) => {
        let tmp_path = cur_path + '.' + key
        let deal = false
        for (let i = 0; i < dec_infos["short_keys"].length; i++) {
            if (dec_infos["short_keys"][i] === key) {
                let len = dec_infos["real_keys"][i].length
                if (tmp_path.slice(tmp_path.length - len) === dec_infos["real_keys"][i]) {
                    dec_list.push(tmp_path)
                    dec_index_list.push(i)
                    if (!deal && traverse_all && typeof (obj[key]) === 'object') {
                        obj_property_traverse(obj[key], tmp_path, dec_infos, dec_list, dec_index_list, traverse_all)
                    }
                    deal = true
                }
            }
        }
        let value = obj[key]
        if (deal || !value || typeof (value) !== 'object') return
        obj_property_traverse(value, tmp_path, dec_infos, dec_list, dec_index_list, traverse_all)
    })
}

function value_conditional(value, condition_express) {
    function excute_eval(express) {
        try {
            return eval(express)
        } catch (error) {
            return false
        }
    }
    let reg = /(\$text|\$value|\$exist|\$notexist)?((>=|<=|>|<|!~=|!=|~=|=))?(.*)/
    let match = condition_express.match(reg)
    let condition_type = match[1] || '$text'
    let condition_operator = match[2]
    let condition_test_value = match[4]

    if (condition_type === '$value') {
        if (!['>=', '<=', '>', '<', '='].includes(condition_operator)) return false
        if (condition_operator === '=') condition_operator = '==='
        return excute_eval(value + condition_operator + condition_test_value)
    }
    if (condition_type === '$exist') {
        return excute_eval('value !== undefined && value !== null')
    }
    if (condition_type === '$notexist') {
        return excute_eval('value === undefined || value === null')
    }
    if (condition_type === '$text') {
        if (typeof (value) === 'object') value = JSON.stringify(value)
        if (['>=', '<=', '>', '<'].includes(condition_operator)) {
            return excute_eval(value.length + condition_operator + condition_test_value.length)
        }
        if (['=', '!=', '!~=', '~='].includes(condition_operator)) {
            let result = condition_operator === '=' ? value === condition_test_value : new RegExp(condition_test_value).test(value)
            return ['!=', '!~='].includes(condition_operator) ? !result : result
        }
    }
    return false
}