YouTube去广告

这是一个去除YouTube广告的脚本,轻量且高效,它能丝滑的去除界面广告和视频广告,包括6s广告。

当前为 2023-09-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube去广告 YouTube AD Blocker
  3. // @name:zh-CN YouTube去广告
  4. // @name:zh-TW YouTube去廣告
  5. // @name:zh-HK YouTube去廣告
  6. // @name:zh-MO YouTube去廣告
  7. // @namespace http://tampermonkey.net/
  8. // @version 5.7
  9. // @description 这是一个去除YouTube广告的脚本,轻量且高效,它能丝滑的去除界面广告和视频广告,包括6s广告。This is a script that removes ads on YouTube, it's lightweight and efficient, capable of smoothly removing interface and video ads, including 6s ads.
  10. // @description:zh-CN 这是一个去除YouTube广告的脚本,轻量且高效,它能丝滑的去除界面广告和视频广告,包括6s广告。
  11. // @description:zh-TW 這是一個去除YouTube廣告的腳本,輕量且高效,它能絲滑地去除界面廣告和視頻廣告,包括6s廣告。
  12. // @description:zh-HK 這是一個去除YouTube廣告的腳本,輕量且高效,它能絲滑地去除界面廣告和視頻廣告,包括6s廣告。
  13. // @description:zh-MO 這是一個去除YouTube廣告的腳本,輕量且高效,它能絲滑地去除界面廣告和視頻廣告,包括6s廣告。
  14. // @author iamfugui
  15. // @match *://*.youtube.com/*
  16. // @icon https://www.google.com/s2/favicons?sz=64&domain=YouTube.com
  17. // @grant none
  18. // @license MIT
  19. // ==/UserScript==
  20. (function() {
  21. `use strict`;
  22.  
  23. //界面广告选择器
  24. const cssSeletorArr = [
  25. `#masthead-ad`,//首页顶部横幅广告.
  26. `ytd-rich-item-renderer.style-scope.ytd-rich-grid-row #content:has(.ytd-display-ad-renderer)`,//首页视频排版广告.
  27. `ytd-rich-section-renderer #dismissible`,//首页中部横幅广告.
  28. `.video-ads.ytp-ad-module`,//播放器底部广告.
  29. `tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)`,//播放页会员促销广告.
  30. `#panels:has(*[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. `ad-slot-renderer`,//M播放页第三方推荐广告
  36. `ytm-companion-ad-renderer`,//M可跳过的视频广告链接处
  37. ];
  38. const dev = true;//开发使用
  39. let video;//视频dom
  40.  
  41. /**
  42. * 将标准时间格式化
  43. * @param {Date} time 标准时间
  44. * @param {String} format 格式
  45. * @return {String}
  46. */
  47. function moment(time, format = `YYYY-MM-DD HH:mm:ss`) {
  48. // 获取年⽉⽇时分秒
  49. let y = time.getFullYear()
  50. let m = (time.getMonth() + 1).toString().padStart(2, `0`)
  51. let d = time.getDate().toString().padStart(2, `0`)
  52. let h = time.getHours().toString().padStart(2, `0`)
  53. let min = time.getMinutes().toString().padStart(2, `0`)
  54. let s = time.getSeconds().toString().padStart(2, `0`)
  55. if (format === `YYYY-MM-DD`) {
  56. return `${y}-${m}-${d}`
  57. } else {
  58. return `${y}-${m}-${d} ${h}:${min}:${s}`
  59. }
  60. }
  61.  
  62. /**
  63. * 输出信息
  64. * @param {String} msg 信息
  65. * @return {undefined}
  66. */
  67. function log(msg) {
  68. if(!dev){
  69. return false;
  70. }
  71. console.log(`${moment(new Date())} ${msg}`)
  72. }
  73.  
  74. /**
  75. * 获取当前url的参数,如果要查询特定参数请传参
  76. * @param {String} 要查询的参数
  77. * @return {String || Object}
  78. */
  79. function getUrlParams(param) {
  80. // 通过 ? 分割获取后面的参数字符串
  81. let urlStr = location.href.split(`?`)[1]
  82. if(!urlStr){
  83. return ``;
  84. }
  85. // 创建空对象存储参数
  86. let obj = {};
  87. // 再通过 & 将每一个参数单独分割出来
  88. let paramsArr = urlStr.split(`&`)
  89. for(let i = 0,len = paramsArr.length;i < len;i++){
  90. // 再通过 = 将每一个参数分割为 key:value 的形式
  91. let arr = paramsArr[i].split(`=`)
  92. obj[arr[0]] = arr[1];
  93. }
  94.  
  95. if(!param){
  96. return obj;
  97. }
  98.  
  99. return obj[param]||``;
  100. }
  101.  
  102. /**
  103. * 生成去除广告的css元素style并附加到HTML节点上
  104. * @param {String} styles 样式文本
  105. * @return {undefined}
  106. */
  107. function generateRemoveADHTMLElement(styles) {
  108. //如果已经设置过,退出.
  109. if (document.getElementById(`RemoveADHTMLElement`)) {
  110. log(`屏蔽页面广告节点已生成`);
  111. return false
  112. }
  113.  
  114. //设置移除广告样式.
  115. let style = document.createElement(`style`);//创建style元素.
  116. style.id = `RemoveADHTMLElement`;
  117. (document.querySelector(`head`) || document.querySelector(`body`)).appendChild(style);//将节点附加到HTML.
  118. style.appendChild(document.createTextNode(styles));//附加样式节点到元素节点.
  119. log(`生成屏蔽页面广告节点成功`)
  120.  
  121. }
  122.  
  123. /**
  124. * 生成去除广告的css文本
  125. * @param {Array} cssSeletorArr 待设置css选择器数组
  126. * @return {String}
  127. */
  128. function generateRemoveADCssText(cssSeletorArr){
  129. cssSeletorArr.forEach((seletor,index)=>{
  130. cssSeletorArr[index]=`${seletor}{display:none!important}`;//遍历并设置样式.
  131. });
  132. return cssSeletorArr.join(` `);//拼接成字符串.
  133. }
  134.  
  135. /**
  136. * 触摸事件
  137. * @return {undefined}
  138. */
  139. function nativeTouch(){
  140. const minNum = 100;
  141. const maxNum = 999;
  142. const randomNum = (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum)/1000;
  143.  
  144. let element =this;
  145. // 创建 Touch 对象
  146. let touch = new Touch({
  147. identifier: Date.now(),
  148. target: element,
  149. clientX: 111+randomNum,
  150. clientY: 222+randomNum,
  151. radiusX: 333+randomNum,
  152. radiusY: 444+randomNum,
  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. element.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. element.dispatchEvent(touchEndEvent);
  182. }
  183.  
  184. /**
  185. * 跳过广告
  186. * @return {undefined}
  187. */
  188. function skipAd(mutationsList, observer) {
  189. let skipButton = document.querySelector(`.ytp-ad-skip-button`);
  190. let shortAdMsg = document.querySelector(`.video-ads.ytp-ad-module .ytp-ad-player-overlay`);
  191.  
  192. if(!skipButton && !shortAdMsg){
  193. log(`******广告结束变动******`);
  194. return false;
  195. }
  196.  
  197. const fn = () => {
  198. //拥有跳过按钮的广告.
  199. if(skipButton)
  200. {
  201. log(`普通视频广告~~~~~~~~~~~~~`);
  202. log(`总时长:`);
  203. log(`${video.duration}`)
  204. log(`当前时间:`);
  205. log(`${video.currentTime}`)
  206. // 跳过广告.
  207. skipButton.click();
  208. nativeTouch.call(skipButton);
  209. log(`按钮跳过了该广告~~~~~~~~~~~~~`);
  210. return false;//终止
  211. }
  212.  
  213. //没有跳过按钮的短广告.
  214. if(shortAdMsg){
  215. if(video.duration === NaN || video.duration === `NaN`){
  216. document.querySelector(`.ad-showing video`).currentTime=1024;
  217. log(`youtube行为改变~~~~~~~~~~~~~`);
  218. log(`强制结束了该广告~~~~~~~~~~~~~`);
  219. //alert(`youtube行为改变`);
  220. return false;//终止
  221. }
  222. log(`强制视频广告~~~~~~~~~~~~~`);
  223. log(`总时长:`);
  224. log(`${video.duration}`)
  225. log(`当前时间:`);
  226. log(`${video.currentTime}`)
  227. video.currentTime = 1024;
  228. log(`强制结束了该广告~~~~~~~~~~~~~`);
  229. return false;//终止
  230. }
  231. log(`######广告此前已关闭######`);
  232. }
  233. fn();//标准执行
  234. }
  235.  
  236. /**
  237. * 去除播放中的广告
  238. * @return {undefined}
  239. */
  240. function removePlayerAD(){
  241.  
  242. //如果已经在运行,退出.
  243. if (document.getElementById(`removePlayerAD`)) {
  244. log(`去除播放中的广告功能已在运行`);
  245. return false
  246. }
  247. //设置运行tag.
  248. let style = document.createElement(`style`);
  249. style.id = `removePlayerAD`;
  250. (document.querySelector(`head`) || document.querySelector(`body`)).appendChild(style);//将节点附加到HTML.
  251.  
  252.  
  253. let observer;//监听器
  254.  
  255. //开始监听
  256. function startObserve(){
  257. video = document.querySelector(`video`);//获取视频节点
  258.  
  259. //广告节点监听
  260. const targetNode = document.querySelector(`.video-ads.ytp-ad-module`);
  261.  
  262. //这个视频不存在广告
  263. if(!targetNode){
  264. log(`这个视频不存在广告`);
  265. return false;
  266. }
  267.  
  268. //监听视频中的广告并处理
  269. const config = {childList: true, subtree: true };// 监听目标节点本身与子树下节点的变动
  270. observer = new MutationObserver(skipAd);// 创建一个观察器实例并设置处理广告的回调函数
  271. observer.observe(targetNode, config);// 以上述配置开始观察广告节点
  272.  
  273. //初始化监听,发现并处理广告
  274. let skipButton = document.querySelector(`.ytp-ad-skip-button`);
  275. let shortAdMsg = document.querySelector(`.video-ads.ytp-ad-module .ytp-ad-player-overlay`);
  276. if(skipButton || shortAdMsg){
  277. log(`初始化监听,发现并处理广告`);
  278. skipAd();
  279. }else{
  280. log(`初始化监听,没有发现广告`);
  281. }
  282.  
  283. }
  284.  
  285. //结束监听
  286. function closeObserve(){
  287. observer.disconnect();
  288. observer = null;
  289. }
  290.  
  291. //轮询任务
  292. setInterval(function(){
  293. //视频播放页
  294. if(getUrlParams(`v`)){
  295. if(observer){
  296. return false;
  297. }
  298. startObserve();
  299. }else{
  300. //其它界面
  301. if(!observer){
  302. return false;
  303. }
  304. closeObserve();
  305. }
  306. },16);
  307.  
  308. log(`运行去除播放中的广告功能成功`)
  309. }
  310.  
  311. /**
  312. * main函数
  313. */
  314. function main(){
  315. generateRemoveADHTMLElement(generateRemoveADCssText(cssSeletorArr));//移除界面中的广告.
  316. removePlayerAD();//移除播放中的广告.
  317. }
  318.  
  319. if (document.readyState === `loading`) {
  320. log(`YouTube去广告脚本即将调用:`);
  321. document.addEventListener(`DOMContentLoaded`, main);// 此时加载尚未完成
  322. } else {
  323. log(`YouTube去广告脚本快速调用:`);
  324. main();// 此时`DOMContentLoaded` 已经被触发
  325. }
  326.  
  327. })();