返回首頁 

Greasy Fork is available in English.

youtube-adb

A script to remove YouTube ads, including static ads and video ads, without interfering with the network and ensuring safety.

  1. // ==UserScript==// @name youtube-adb// @name:zh-CN YouTube去广告// @name:zh-TW YouTube去廣告// @name:zh-HK YouTube去廣告// @name:zh-MO YouTube去廣告// @namespace https://github.com/iamfugui/youtube-adb// @version 6.21// @description A script to remove YouTube ads, including static ads and video ads, without interfering with the network and ensuring safety.// @description:zh-CN 脚本用于移除YouTube广告,包括静态广告和视频广告。不会干扰网络,安全。// @description:zh-TW 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。// @description:zh-HK 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。// @description:zh-MO 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。// @match *://*.youtube.com/*// @exclude *://accounts.youtube.com/*// @exclude *://www.youtube.com/live_chat_replay*// @exclude *://www.youtube.com/persist_identity*// @icon https://www.google.com/s2/favicons?sz=64&domain=YouTube.com// @grant none// @license MIT// ==/UserScript==(function() {`use strict`;let video;//界面广告选择器const cssSelectorArr = [`#masthead-ad`,//首页顶部横幅广告.`ytd-rich-item-renderer.style-scope.ytd-rich-grid-row #content:has(.ytd-display-ad-renderer)`,//首页视频排版广告.`.video-ads.ytp-ad-module`,//播放器底部广告.`tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)`,//播放页会员促销广告.`ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]`,//播放页右上方推荐广告.`#related #player-ads`,//播放页评论区右侧推广广告.`#related ytd-ad-slot-renderer`,//播放页评论区右侧视频排版广告.`ytd-ad-slot-renderer`,//搜索页广告.`yt-mealbar-promo-renderer`,//播放页会员推荐广告.`ytd-popup-container:has(a[href="/premium"])`,//会员拦截广告`ad-slot-renderer`,//M播放页第三方推荐广告`ytm-companion-ad-renderer`,//M可跳过的视频广告链接处];window.dev=false;//开发使用/*** 将标准时间格式化* @param {Date} time 标准时间* @param {String} format 格式* @return {String}*/function moment(time) {// 获取年⽉⽇时分秒let y = time.getFullYear()let m = (time.getMonth() + 1).toString().padStart(2, `0`)let d = time.getDate().toString().padStart(2, `0`)let h = time.getHours().toString().padStart(2, `0`)let min = time.getMinutes().toString().padStart(2, `0`)let s = time.getSeconds().toString().padStart(2, `0`)return `${y}-${m}-${d} ${h}:${min}:${s}`}/*** 输出信息* @param {String} msg 信息* @return {undefined}*/function log(msg) {if(!window.dev){return false;}console.log(window.location.href);console.log(`${moment(new Date())} ${msg}`);}/*** 设置运行标志* @param {String} name* @return {undefined}*/function setRunFlag(name){let style = document.createElement(`style`);style.id = name;(document.head || document.body).appendChild(style);//将节点附加到HTML.}/*** 获取运行标志* @param {String} name* @return {undefined|Element}*/function getRunFlag(name){return document.getElementById(name);}/*** 检查是否设置了运行标志* @param {String} name* @return {Boolean}*/function checkRunFlag(name){if(getRunFlag(name)){return true;}else{setRunFlag(name)return false;}}/*** 生成去除广告的css元素style并附加到HTML节点上* @param {String} styles 样式文本* @return {undefined}*/function generateRemoveADHTMLElement(id) {//如果已经设置过,退出.if (checkRunFlag(id)) {log(`屏蔽页面广告节点已生成`);return false}//设置移除广告样式.let style = document.createElement(`style`);//创建style元素.(document.head || document.body).appendChild(style);//将节点附加到HTML.style.appendChild(document.createTextNode(generateRemoveADCssText(cssSelectorArr)));//附加样式节点到元素节点.log(`生成屏蔽页面广告节点成功`);}/*** 生成去除广告的css文本* @param {Array} cssSelectorArr 待设置css选择器数组* @return {String}*/function generateRemoveADCssText(cssSelectorArr){cssSelectorArr.forEach((selector,index)=>{cssSelectorArr[index]=`${selector}{display:none!important}`;//遍历并设置样式.});return cssSelectorArr.join(` `);//拼接成字符串.}/*** 触摸事件* @return {undefined}*/function nativeTouch(){// 创建 Touch 对象let touch = new Touch({identifier: Date.now(),target: this,clientX: 12,clientY: 34,radiusX: 56,radiusY: 78,rotationAngle: 0,force: 1});// 创建 TouchEvent 对象let touchStartEvent = new TouchEvent(`touchstart`, {bubbles: true,cancelable: true,view: window,touches: [touch],targetTouches: [touch],changedTouches: [touch]});// 分派 touchstart 事件到目标元素this.dispatchEvent(touchStartEvent);// 创建 TouchEvent 对象let touchEndEvent = new TouchEvent(`touchend`, {bubbles: true,cancelable: true,view: window,touches: [],targetTouches: [],changedTouches: [touch]});// 分派 touchend 事件到目标元素this.dispatchEvent(touchEndEvent);}/*** 获取dom* @return {undefined}*/function getVideoDom(){video = document.querySelector(`.ad-showing video`) || document.querySelector(`video`);}/*** 自动播放* @return {undefined}*/function playAfterAd(){if(video.paused && video.currentTime<1){video.play();log(`自动播放视频`);}}/*** 移除YT拦截广告拦截弹窗并且关闭关闭遮罩层* @return {undefined}*/function closeOverlay(){//移除YT拦截广告拦截弹窗const premiumContainers = [...document.querySelectorAll(`ytd-popup-container`)];const matchingContainers = premiumContainers.filter(container => container.querySelector(`a[href="/premium"]`));if(matchingContainers.length>0){matchingContainers.forEach(container => container.remove());log(`移除YT拦截器`);}// 获取所有具有指定标签的元素const backdrops = document.querySelectorAll(`tp-yt-iron-overlay-backdrop`);// 查找具有特定样式的元素const targetBackdrop = Array.from(backdrops).find((backdrop) => backdrop.style.zIndex === `2201`);// 如果找到该元素,清空其类并移除 open 属性if (targetBackdrop) {targetBackdrop.className = ``; // 清空所有类targetBackdrop.removeAttribute(`opened`); // 移除 open 属性log(`关闭遮罩层`);}}/*** 跳过广告* @return {undefined}*/function skipAd(mutationsList, observer) {const skipButton = document.querySelector(`.ytp-ad-skip-button`) || document.querySelector(`.ytp-skip-ad-button`) || document.querySelector(`.ytp-ad-skip-button-modern`);const shortAdMsg = document.querySelector(`.video-ads.ytp-ad-module .ytp-ad-player-overlay`) || document.querySelector(`.ytp-ad-button-icon`);if((skipButton || shortAdMsg) && window.location.href.indexOf(`https://m.youtube.com/`) === -1){ //移动端静音有bugvideo.muted = true;}if(skipButton){const delayTime = 0.5;setTimeout(skipAd,delayTime*1000);//如果click和call没有跳过更改,直接更改广告时间if(video.currentTime>delayTime){video.currentTime = video.duration;//强制log(`特殊账号跳过按钮广告`);return;}skipButton.click();//PCnativeTouch.call(skipButton);//Phonelog(`按钮跳过广告`);}else if(shortAdMsg){video.currentTime = video.duration;//强制log(`强制结束了该广告`);}}/*** 去除播放中的广告* @return {undefined}*/function removePlayerAD(id){//如果已经在运行,退出.if (checkRunFlag(id)) {log(`去除播放中的广告功能已在运行`);return false}//监听视频中的广告并处理const targetNode = document.body;//直接监听body变动const config = {childList: true, subtree: true };// 监听目标节点本身与子树下节点的变动const observer = new MutationObserver(()=>{getVideoDom();closeOverlay();skipAd();playAfterAd();});//处理视频广告相关observer.observe(targetNode, config);// 以上述配置开始观察广告节点log(`运行去除播放中的广告功能成功`);}/*** main函数*/function main(){generateRemoveADHTMLElement(`removeADHTMLElement`);//移除界面中的广告.removePlayerAD(`removePlayerAD`);//移除播放中的广告.}if (document.readyState === `loading`) {document.addEventListener(`DOMContentLoaded`, main);// 此时加载尚未完成log(`YouTube去广告脚本即将调用:`);} else {main();// 此时`DOMContentLoaded` 已经被触发log(`YouTube去广告脚本快速调用:`);}let r###meVideo = () => {const videoelem = document.body.querySelector('video.html5-main-video')if (videoelem && videoelem.paused) {console.log('r###me video')videoelem.play()}}let removePop = node => {const elpopup = node.querySelector('.ytd-popup-container > .ytd-popup-container > .ytd-enforcement-message-view-model')if (elpopup) {elpopup.parentNode.remove()console.log('remove popup', elpopup)const bdelems = document.getElementsByTagName('tp-yt-iron-overlay-backdrop')for (var x = (bdelems || []).length; x--;)bdelems[x].remove()r###meVideo()}if (node.tagName.toLowerCase() === 'tp-yt-iron-overlay-backdrop') {node.remove()r###meVideo()console.log('remove backdrop', node)}}let obs = new MutationObserver(mutations => mutations.forEach(mutation => {if (mutation.type === 'childList') {Array.from(mutation.addedNodes).filter(node => node.nodeType === 1).map(node => removePop(node))}}))// have the observer observe foo for changes in childrenobs.observe(document.body, {childList: true,subtree: true})})();