YouTube去广告

脚本用于移除YouTube广告,包括静态广告和视频广告。不会干扰网络,安全。

当前为 2024-07-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name youtube-adb
  3. // @name:zh-CN YouTube去广告
  4. // @name:zh-TW YouTube去廣告
  5. // @name:zh-HK YouTube去廣告
  6. // @name:zh-MO YouTube去廣告
  7. // @namespace https://github.com/iamfugui/youtube-adb
  8. // @version 6.16
  9. // @description A script to remove YouTube ads, including static ads and video ads, without interfering with the network and ensuring safety.
  10. // @description:zh-CN 脚本用于移除YouTube广告,包括静态广告和视频广告。不会干扰网络,安全。
  11. // @description:zh-TW 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。
  12. // @description:zh-HK 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。
  13. // @description:zh-MO 腳本用於移除 YouTube 廣告,包括靜態廣告和視頻廣告。不會干擾網路,安全。
  14. // @match *://*.youtube.com/*
  15. // @exclude *://accounts.youtube.com/*
  16. // @exclude *://www.youtube.com/live_chat_replay*
  17. // @exclude *://www.youtube.com/persist_identity*
  18. // @icon https://www.google.com/s2/favicons?sz=64&domain=YouTube.com
  19. // @grant none
  20. // @license MIT
  21. // ==/UserScript==
  22. (function() {
  23. `use strict`;
  24. //界面广告选择器
  25. const cssSelectorArr = [
  26. `#masthead-ad`,//首页顶部横幅广告.
  27. `ytd-rich-item-renderer.style-scope.ytd-rich-grid-row #content:has(.ytd-display-ad-renderer)`,//首页视频排版广告.
  28. `.video-ads.ytp-ad-module`,//播放器底部广告.
  29. `tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)`,//播放页会员促销广告.
  30. `ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]`,//播放页右上方推荐广告.
  31. `#related #player-ads`,//播放页评论区右侧推广广告.
  32. `#related ytd-ad-slot-renderer`,//播放页评论区右侧视频排版广告.
  33. `ytd-ad-slot-renderer`,//搜索页广告.
  34. `yt-mealbar-promo-renderer`,//播放页会员推荐广告.
  35. //`tp-yt-iron-overlay-backdrop[style*="z-index: 2201;"]`,//会员拦截广告
  36. `ytd-popup-container:has(a[href="/premium"])`,//会员拦截广告
  37. `ad-slot-renderer`,//M播放页第三方推荐广告
  38. `ytm-companion-ad-renderer`,//M可跳过的视频广告链接处
  39. ];
  40.  
  41. window.dev=false;//开发使用
  42.  
  43. /**
  44. * 将标准时间格式化
  45. * @param {Date} time 标准时间
  46. * @param {String} format 格式
  47. * @return {String}
  48. */
  49. function moment(time) {
  50. // 获取年⽉⽇时分秒
  51. let y = time.getFullYear()
  52. let m = (time.getMonth() + 1).toString().padStart(2, `0`)
  53. let d = time.getDate().toString().padStart(2, `0`)
  54. let h = time.getHours().toString().padStart(2, `0`)
  55. let min = time.getMinutes().toString().padStart(2, `0`)
  56. let s = time.getSeconds().toString().padStart(2, `0`)
  57. return `${y}-${m}-${d} ${h}:${min}:${s}`
  58. }
  59.  
  60. /**
  61. * 输出信息
  62. * @param {String} msg 信息
  63. * @return {undefined}
  64. */
  65. function log(msg) {
  66. if(!window.dev){
  67. return false;
  68. }
  69. console.log(window.location.href);
  70. console.log(`${moment(new Date())} ${msg}`);
  71. }
  72.  
  73. /**
  74. * 设置运行标志
  75. * @param {String} name
  76. * @return {undefined}
  77. */
  78. function setRunFlag(name){
  79. let style = document.createElement(`style`);
  80. style.id = name;
  81. (document.head || document.body).appendChild(style);//将节点附加到HTML.
  82. }
  83.  
  84. /**
  85. * 获取运行标志
  86. * @param {String} name
  87. * @return {undefined|Element}
  88. */
  89. function getRunFlag(name){
  90. return document.getElementById(name);
  91. }
  92.  
  93. /**
  94. * 检查是否设置了运行标志
  95. * @param {String} name
  96. * @return {Boolean}
  97. */
  98. function checkRunFlag(name){
  99. if(getRunFlag(name)){
  100. return true;
  101. }else{
  102. setRunFlag(name)
  103. return false;
  104. }
  105. }
  106.  
  107. /**
  108. * 生成去除广告的css元素style并附加到HTML节点上
  109. * @param {String} styles 样式文本
  110. * @return {undefined}
  111. */
  112. function generateRemoveADHTMLElement(id) {
  113. //如果已经设置过,退出.
  114. if (checkRunFlag(id)) {
  115. log(`屏蔽页面广告节点已生成`);
  116. return false
  117. }
  118.  
  119. //设置移除广告样式.
  120. let style = document.createElement(`style`);//创建style元素.
  121. (document.head || document.body).appendChild(style);//将节点附加到HTML.
  122. style.appendChild(document.createTextNode(generateRemoveADCssText(cssSelectorArr)));//附加样式节点到元素节点.
  123. log(`生成屏蔽页面广告节点成功`);
  124. }
  125.  
  126. /**
  127. * 生成去除广告的css文本
  128. * @param {Array} cssSelectorArr 待设置css选择器数组
  129. * @return {String}
  130. */
  131. function generateRemoveADCssText(cssSelectorArr){
  132. cssSelectorArr.forEach((selector,index)=>{
  133. cssSelectorArr[index]=`${selector}{display:none!important}`;//遍历并设置样式.
  134. });
  135. return cssSelectorArr.join(` `);//拼接成字符串.
  136. }
  137.  
  138. /**
  139. * 触摸事件
  140. * @return {undefined}
  141. */
  142. function nativeTouch(){
  143. // 创建 Touch 对象
  144. let touch = new Touch({
  145. identifier: Date.now(),
  146. target: this,
  147. clientX: 12,
  148. clientY: 34,
  149. radiusX: 56,
  150. radiusY: 78,
  151. rotationAngle: 0,
  152. force: 1
  153. });
  154.  
  155. // 创建 TouchEvent 对象
  156. let touchStartEvent = new TouchEvent(`touchstart`, {
  157. bubbles: true,
  158. cancelable: true,
  159. view: window,
  160. touches: [touch],
  161. targetTouches: [touch],
  162. changedTouches: [touch]
  163. });
  164.  
  165. // 分派 touchstart 事件到目标元素
  166. this.dispatchEvent(touchStartEvent);
  167.  
  168. // 创建 TouchEvent 对象
  169. let touchEndEvent = new TouchEvent(`touchend`, {
  170. bubbles: true,
  171. cancelable: true,
  172. view: window,
  173. touches: [],
  174. targetTouches: [],
  175. changedTouches: [touch]
  176. });
  177.  
  178. // 分派 touchend 事件到目标元素
  179. this.dispatchEvent(touchEndEvent);
  180. }
  181.  
  182.  
  183. /**
  184. * 自动播放
  185. * @return {undefined}
  186. */
  187. function autoPlayAfterAd(){
  188. setInterval(()=>{
  189. let video = document.querySelector('.ad-showing video') || document.querySelector('video');
  190. if(video.paused && video.currentTime<1){
  191. video.play();
  192. log("自动播放视频");
  193. }
  194. },16)
  195. }
  196.  
  197.  
  198. /**
  199. * 跳过广告
  200. * @return {undefined}
  201. */
  202. function skipAd(mutationsList, observer) {
  203. let video = document.querySelector(`.ad-showing video`) || document.querySelector(`video`);//获取视频节点
  204. let skipButton = document.querySelector(`.ytp-ad-skip-button`) || document.querySelector(`.ytp-skip-ad-button`) || document.querySelector(`.ytp-ad-skip-button-modern`);
  205. let shortAdMsg = document.querySelector(`.video-ads.ytp-ad-module .ytp-ad-player-overlay`) || document.querySelector(`.ytp-ad-button-icon`);
  206.  
  207. //移除YT拦截广告拦截弹窗
  208. const premiumContainers = [...document.querySelectorAll(`ytd-popup-container`)];
  209. const matchingContainers = premiumContainers.filter(container => container.querySelector(`a[href="/premium"]`));
  210.  
  211. if(matchingContainers.length>0){
  212. matchingContainers.forEach(container => container.remove());
  213. log(`移除YT拦截器`);
  214. }
  215.  
  216. // 获取所有具有指定标签的元素
  217. const backdrops = document.querySelectorAll('tp-yt-iron-overlay-backdrop');
  218. // 查找具有特定样式的元素
  219. const targetBackdrop = Array.from(backdrops).find(
  220. (backdrop) => backdrop.style.zIndex === '2201'
  221. );
  222. // 如果找到该元素,清空其类并移除 open 属性
  223. if (targetBackdrop) {
  224. targetBackdrop.className = ''; // 清空所有类
  225. targetBackdrop.removeAttribute('opened'); // 移除 open 属性
  226. log(`关闭遮罩层`);
  227. }
  228.  
  229.  
  230. if((skipButton || shortAdMsg) && window.location.href.indexOf("https://m.youtube.com/") === -1){ //移动端静音有bug
  231. video.muted = true;
  232. //video.playbackRate = 16;
  233. //video.play();
  234. //log(`调速~~~~~~~~~~~~~`);
  235. }
  236.  
  237. if(skipButton){
  238. if(video.currentTime>0.5){
  239. video.currentTime = video.duration;//强制
  240. log(`特殊账号跳过按钮广告~~~~~~~~~~~~~`);
  241. return;
  242. }
  243. skipButton.click();//PC
  244. nativeTouch.call(skipButton);//Phone
  245. log(`按钮跳过广告~~~~~~~~~~~~~`);
  246. }else if(shortAdMsg && video.currentTime>0.5){
  247. video.currentTime = video.duration;//强制
  248. log(`强制结束了该广告`);
  249. }
  250.  
  251. }
  252.  
  253. /**
  254. * 去除播放中的广告
  255. * @return {undefined}
  256. */
  257. function removePlayerAD(id){
  258. //如果已经在运行,退出.
  259. if (checkRunFlag(id)) {
  260. log(`去除播放中的广告功能已在运行`);
  261. return false
  262. }
  263. let observer;//监听器
  264. let timerID;//定时器
  265.  
  266. //开始监听
  267. function startObserve(){
  268. //广告节点监听
  269. const targetNode = document.querySelector(`.video-ads.ytp-ad-module`);
  270. if(!targetNode){
  271. log(`正在寻找待监听的目标节点`);
  272. return false;
  273. }
  274. //监听视频中的广告并处理
  275. const config = {childList: true, subtree: true };// 监听目标节点本身与子树下节点的变动
  276. observer = new MutationObserver(skipAd);// 创建一个观察器实例并设置处理广告的回调函数
  277. observer.observe(targetNode, config);// 以上述配置开始观察广告节点
  278. timerID=setInterval(skipAd, 16);//漏网鱼
  279. }
  280.  
  281. //轮询任务
  282. let startObserveID = setInterval(()=>{
  283. if(observer && timerID){
  284. clearInterval(startObserveID);
  285. }else{
  286. startObserve();
  287. }
  288. },16);
  289.  
  290. log(`运行去除播放中的广告功能成功`);
  291. }
  292.  
  293. /**
  294. * main函数
  295. */
  296. function main(){
  297. generateRemoveADHTMLElement(`removeADHTMLElement`);//移除界面中的广告.
  298. removePlayerAD(`removePlayerAD`);//移除播放中的广告.
  299. autoPlayAfterAd();//自动播放被暂停的视频
  300. }
  301.  
  302. if (document.readyState === `loading`) {
  303. log(`YouTube去广告脚本即将调用:`);
  304. document.addEventListener(`DOMContentLoaded`, main);// 此时加载尚未完成
  305. } else {
  306. log(`YouTube去广告脚本快速调用:`);
  307. main();// 此时`DOMContentLoaded` 已经被触发
  308. }
  309.  
  310. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址