// ==UserScript== // @name youtube广告拦截 // @namespace http://tampermonkey.net/ // @version 1.1.4 // @description 拦截所有youtube广告,不留白,不闪屏,体验第一。已适配移动端,支持自定义拦截 // @author hua // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @connect https://update.greasyfork.icu/ // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== const open_config_keyword = '2333' 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); const script_url = 'https://update.greasyfork.icu/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 let open_debugger = false let isinint = false let mobile_web url_observer() init() function init() { log('初始化开始!', 0) config_init() let ytInitialPlayerResponse_value = unsafeWindow['ytInitialPlayerResponse'] Object.defineProperty(unsafeWindow, 'ytInitialPlayerResponse', { get: function () { return ytInitialPlayerResponse_value }, set: function (value) { let start_time = new Date().getTime() value && obj_process(value, ytInitialPlayerResponse_ad_rule, true) log('ytInitialPlayerResponse 时间:', new Date().getTime() - start_time, 1); ytInitialPlayerResponse_value = value } }); let ytInitialData_value = unsafeWindow['ytInitialData'] Object.defineProperty(unsafeWindow, 'ytInitialData', { get: function () { return ytInitialData_value }, set: function (value) { let start_time = new Date().getTime() if (/watch/.test(href)) { value && obj_process(value, watch_page_ytInitialData_ad_rule, true) ytInitialData_value = value } else { value && obj_process(value, home_page_ytInitialData_ad_rule, true) ytInitialData_value = value } log('ytInitialData 时间:', new Date().getTime() - start_time, 1); } }); Object.defineProperty(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; Object.defineProperty(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', 1); 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, 1); return new Response(result, response) } const originFetch = fetch; unsafeWindow.fetch = (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) { return await deal_resposn('browse', response, home_page_ytInitialData_ad_rule) } if (url.indexOf('https://m.youtube.com/youtubei/v1/guide') > -1) { return await deal_resposn('guide', response, home_page_ytInitialData_ad_rule) } return return_response } return originFetch(uri, options).then(fetch_request); } document.addEventListener('DOMContentLoaded', function () { !mobile_web && search_listener() checke_update() }) isinint = true log('初始化结束!', 0) } function config_init() { let column_recommend_rule let item_label_fifter_rule let watch_page_item_label_fifter_rule let home_page_item_label_fifter_rule mobile_web = href.indexOf('https://m.youtube.com/') > -1 ytInitialPlayerResponse_ad_rule = watch_page_ytInitialData_ad_rule = home_page_ytInitialData_ad_rule = null //打开直播频道 let open_live_channel = false if (href.indexOf('channel/UC4R8DWoMoI7CAwX8_LjQHig') > -1) open_live_channel = true 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.....=- ~=赞助商广告' if (!open_recommend_movie) ad_label += '|免费(含广告)' 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........=- ~=直播') home_page_item_label_fifter_rule = item_label_fifter_rule watch_page_item_label_fifter_rule = item_label_fifter_rule } } else { column_recommend_rule = 'richShelfRenderer.title.runs[0].text......=- ~=' let ad_label if (href.indexOf('watch') > -1) { ad_label = 'metadataBadgeRenderer.label.....=- ~=赞助商广告' } else { ad_label = 'metadataBadgeRenderer.label......=- ~=赞助商广告' } if (!open_recommend_movie) ad_label += '|免费(含广告)' 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........=- ~=直播') watch_page_item_label_fifter_rule = item_label_fifter_rule.concat(['metadataBadgeRenderer.label.....=- ~=直播']) home_page_item_label_fifter_rule = item_label_fifter_rule.concat(['metadataBadgeRenderer.label......=- ~=直播']) } } let column_recommend_list = [] if (!open_recommend_shorts) column_recommend_list.push('Shorts') if (!open_recommend_movie) column_recommend_list.push('免费 Primetime 电影') if (!open_recommend_popular) column_recommend_list.push('时下流行') 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|你对这个视频有何看法?|此推荐内容怎么样?', 'richGridRenderer.masthead=-', 'videoOwnerRenderer=- /.purchaseButton.buttonRenderer.text.simpleText~=试用', 'adSlotRenderer..=-', ] 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_shorts || !open_recommend_movie || !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) ytInitialPlayerResponse_ad_rule = [ "abs:playerAds=-", "abs:adPlacements=-", "abs:adBreakHeartbeatParams=-", "abs:adSlots=-", ] if (isinint) { setTimeout(search_listener, 500) } } 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) { setTimeout(function () { if (search_input_node.value === open_config_keyword) { display_config_win() } }, 500) } }; } } function url_observer() { if (unsafeWindow.navigation) { 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() }) unsafeWindow.addEventListener('pushState', function (event) { url_change() }); unsafeWindow.addEventListener('popstate', function (event) { url_change() }) unsafeWindow.addEventListener('hashchange', function (event) { url_change() }) } 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() } function log() { let arguments_arr = [...arguments] let flag = arguments_arr.pop() if (flag === 0 || open_debugger) console.log(...arguments_arr); } 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": "电影推荐", "open_recommend_shorts": "Shorts推荐", "open_recommend_liveroom": "直播推荐", "open_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 = '保存' button.onclick = function () { for (let key in config_info) { GM_setValue(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 = JSON.parse(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) return 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) } } 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.' + 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 > b ? 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 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) } } 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)}`, 1); log('删除属性-->' + arr_express + '[' + index + ']', 1); } 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)}`, 1); log('删除属性-->' + path, 1); } } 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 === '=') { let type_ = eval('typeof (' + path + ')') if (value.match(/\{.*\}/)) value = JSON.parse(value) if (typeof (value) === 'string' && value.match(/\[.*\]/)) value = JSON.parse(value) if (value === 'undefined') value = undefined if (value === 'null') value = null if (type_ === 'number' && !isNaN(value)) value = Number(value) 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)}`, 1); log('修改属性-->' + path, 1); } } } 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.' + 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, 1); 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)) { return condition_operator === '=' ? value === condition_test_value : new RegExp(condition_test_value).test(value) } } return false }