YouTube去广告

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

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

QingJ © 2025

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