YouTube去广告

这个脚本可以移除所有广告,包括所有视频广告.简单高效的YouTube去广告脚本,拒绝花里胡哨.你可以尝试为常量cssSeletorArr定义元素.

当前为 2023-04-23 提交的版本,查看 最新版本

  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.  
  8. // @namespace http://tampermonkey.net/
  9. // @version 1.80
  10.  
  11. // @description 这个脚本可以移除所有广告,包括所有视频广告.简单高效的YouTube去广告脚本,拒绝花里胡哨.你可以尝试为常量cssSeletorArr定义元素.This script can remove all ads, including all video ads. A simple and efficient YouTube ad removal script that refuses to be fancy. You can try to define elements for the constant cssSeletorArr.
  12. // @description:zh-CN 这个脚本可以移除所有广告,包括所有视频广告.简单高效的YouTube去广告脚本,拒绝花里胡哨.你可以尝试为常量cssSeletorArr定义元素.
  13. // @description:zh-TW 這個腳本可以移除所有廣告,包括所有視頻廣告.簡單高效的YouTube去廣告腳本,拒絕花裏胡哨.你可以嘗試為常量cssSeletorArr定義元素.
  14. // @description:zh-HK 這個腳本可以移除所有廣告,包括所有視頻廣告.簡單高效的YouTube去廣告腳本,拒絕花裏胡哨.你可以嘗試為常量cssSeletorArr定義元素.
  15. // @description:zh-MO 這個腳本可以移除所有廣告,包括所有視頻廣告.簡單高效的YouTube去廣告腳本,拒絕花裏胡哨.你可以嘗試為常量cssSeletorArr定義元素.
  16.  
  17. // @author iamfugui
  18. // @match *://*.youtube.com/*
  19. // @icon https://www.google.com/s2/favicons?sz=64&domain=YouTube.com
  20. // @grant none
  21. // @license MIT
  22. // ==/UserScript==
  23. (function() {
  24. `use strict`;
  25.  
  26. const dev = false;//开发使用
  27.  
  28. //界面广告选择器
  29. const cssSeletorArr = [
  30. `#masthead-ad`,//首页顶部横幅广告.
  31. `ytd-rich-item-renderer.style-scope.ytd-rich-grid-row #content:has(.ytd-display-ad-renderer)`,//首页视频排版广告.
  32. `ytd-rich-section-renderer #dismissible`,//首页中部横幅广告.
  33. `.video-ads.ytp-ad-module`,//播放器底部广告.
  34. `tp-yt-paper-dialog:has(yt-mealbar-promo-renderer)`,//播放页会员促销广告.
  35. `#related #player-ads`,//播放页评论区右侧推广广告.
  36. `#related ytd-ad-slot-renderer`,//播放页评论区右侧视频排版广告.
  37. `ytd-ad-slot-renderer`,//搜索页广告.
  38. `yt-mealbar-promo-renderer`,//播放页会员推荐广告.
  39. ];
  40.  
  41. let lastTime = parseInt(getUrlParams(`t`))||0;//由于youtube出现广告前是先将进度条归零再进行广告node的更新,故此将上一次进度记录
  42. let currentTime = parseInt(getUrlParams(`t`))||0;//根据url初始化当前播放时间s
  43. let videoLink = `${location.href.split(`&`)[0]}`;//当前视频链接
  44. let video;
  45.  
  46. /**
  47. * 将标准时间格式化
  48. * @param {Date} time 标准时间
  49. * @param {String} format 格式
  50. * @return {String}
  51. */
  52. function moment(time, format = `YYYY-MM-DD HH:mm:ss`) {
  53. // 获取年⽉⽇时分秒
  54. let y = time.getFullYear()
  55. let m = (time.getMonth() + 1).toString().padStart(2, `0`)
  56. let d = time.getDate().toString().padStart(2, `0`)
  57. let h = time.getHours().toString().padStart(2, `0`)
  58. let min = time.getMinutes().toString().padStart(2, `0`)
  59. let s = time.getSeconds().toString().padStart(2, `0`)
  60. if (format === `YYYY-MM-DD`) {
  61. return `${y}-${m}-${d}`
  62. } else {
  63. return `${y}-${m}-${d} ${h}:${min}:${s}`
  64. }
  65. }
  66.  
  67.  
  68. /**
  69. * 输出信息
  70. * @param {String} msg 信息
  71. * @return {undefined}
  72. */
  73. function log(msg) {
  74. if(!dev){
  75. return false;
  76. }
  77. console.log(`${moment(new Date())} ${msg}`)
  78. }
  79.  
  80. /**
  81. * 获取当前url的参数,如果要查询特定参数请传参
  82. * @param {String} 要查询的参数
  83. * @return {String || Object}
  84. */
  85. function getUrlParams(param) {
  86. // 通过 ? 分割获取后面的参数字符串
  87. let urlStr = location.href.split(`?`)[1]
  88. if(!urlStr){
  89. return ``;
  90. }
  91. // 创建空对象存储参数
  92. let obj = {};
  93. // 再通过 & 将每一个参数单独分割出来
  94. let paramsArr = urlStr.split(`&`)
  95. for(let i = 0,len = paramsArr.length;i < len;i++){
  96. // 再通过 = 将每一个参数分割为 key:value 的形式
  97. let arr = paramsArr[i].split(`=`)
  98. obj[arr[0]] = arr[1];
  99. }
  100.  
  101. if(!param){
  102. return obj;
  103. }
  104.  
  105. return obj[param]||``;
  106. }
  107.  
  108. /**
  109. * 得到跳过链接
  110. * @return {String}
  111. */
  112. function getSkipAdUrl(){
  113. let urlParams = getUrlParams();
  114. let url = `${videoLink}`;
  115. for(let key in urlParams){
  116. if(key !== `v` && key !== `t`){
  117. url = `${url}&${key}=${urlParams[key]}`
  118. }
  119. }
  120. return `${url}&t=${parseInt(lastTime)}s`;
  121. }
  122.  
  123. /**
  124. * 生成去除广告的css元素style并附加到HTML节点上
  125. * @param {String} styles 样式文本
  126. * @param {String} styleId 元素id
  127. * @return {undefined}
  128. */
  129. function generateRemoveADHTMLElement(styles,styleId) {
  130. //如果已经设置过,退出.
  131. if (document.getElementById(styleId)) {
  132. return false
  133. }
  134.  
  135. //设置移除广告样式.
  136. let style = document.createElement(`style`);//创建style元素.
  137. style.id = styleId;
  138. (document.querySelector(`head`) || document.querySelector(`body`)).appendChild(style);//将节点附加到HTML.
  139. style.appendChild(document.createTextNode(styles));//附加样式节点到元素节点.
  140. log(`屏蔽页面广告节点已生成`)
  141.  
  142. }
  143.  
  144. /**
  145. * 生成去除广告的css文本
  146. * @param {Array} cssSeletorArr 待设置css选择器数组
  147. * @return {String}
  148. */
  149. function generateRemoveADCssText(cssSeletorArr){
  150. cssSeletorArr.forEach((seletor,index)=>{
  151. cssSeletorArr[index]=`${seletor}{display:none!important}`;//遍历并设置样式.
  152. });
  153. return cssSeletorArr.join(` `);//拼接成字符串.
  154. }
  155.  
  156.  
  157. /**
  158. * 检测用户切换了视频
  159. * @return {undefined}
  160. */
  161. function switchVideoHook(){
  162. if(videoLink !== `${location.href.split(`&`)[0]}`){
  163. videoLink = location.href.split(`&`)[0];//更新链接
  164. lastTime = parseInt(getUrlParams(`t`))||0;//根据url初始化当前播放时间s
  165. currentTime = parseInt(getUrlParams(`t`))||0;//根据url初始化当前播放时间s
  166. log(`检测到用户切换了视频,已更新播放进度`)
  167. }
  168. }
  169.  
  170. /**
  171. * 去除播放中的广告
  172. * @return {undefined}
  173. */
  174. function removePlayerAD(){
  175. let observer;//监听器
  176. let progress;//进度条node
  177. let updateTimerId;//信息更新定时器
  178.  
  179. //点击进度条监听
  180. let clickProgressHandler = function(){
  181. video = document.querySelector(`video`);
  182. lastTime = video.currentTime;//记录播放进度
  183. currentTime = video.currentTime;//记录播放进度
  184.  
  185. log(`进度条监听`);
  186. log(lastTime);
  187. }
  188.  
  189. //开始监听
  190. function startObserve(){
  191. //广告节点监听
  192. const targetNode = document.querySelector(`.video-ads.ytp-ad-module`);
  193.  
  194. //这个视频未存在广告
  195. if(!targetNode){
  196. return false;
  197. }
  198.  
  199. const config = {childList: true, subtree: true };// 监听目标节点本身与子树下节点的变动
  200. // 当观察到变动时执行的回调函数
  201. const callback = function (mutationsList, observer) {
  202. switchVideoHook();//检测用户是否切换了视频
  203.  
  204. //拥有跳过按钮的广告.
  205. let skipButton = document.querySelector(`.ytp-ad-skip-button`);
  206. if(skipButton)
  207. {
  208. skipButton.click();// 跳过广告.
  209. log(`刚刚监听到了广告节点变化并使用按钮跳过了一条广告`);
  210. return false;//终止
  211. }
  212.  
  213. //没有跳过按钮的短广告.
  214. let shortAdMsg = document.querySelector(`.video-ads.ytp-ad-module .ytp-ad-player-overlay`);
  215. if(shortAdMsg){
  216. log(`查看上一次进度${lastTime}`);
  217. log(`查看当前进度${currentTime}`);
  218. video.pause();//暂停播放避免继续请求资源
  219. location.replace(getSkipAdUrl());//得到跳转的url,重新加载.
  220. closeObserve();
  221. return false;//终止
  222. }
  223.  
  224. log(`刚刚监听到了广告节点变化但都没有处理:`);
  225.  
  226. }
  227. // 创建一个观察器实例并传入回调函数
  228. observer = new MutationObserver(callback);
  229. // 以上述配置开始观察广告节点
  230. observer.observe(targetNode, config);
  231.  
  232. //定时更新信息
  233. updateTimerId =setInterval(function(){
  234. switchVideoHook();//检测用户是否切换了视频
  235.  
  236. //如果不是播放页就退出,因为youtube跳转页面时并不会reload页面,所以继续执行定时器,
  237. video = document.querySelector(`video`);
  238. if(!video){
  239. return false;
  240. }
  241. videoLink = location.href.split(`&`)[0];//更新链接
  242. lastTime = currentTime;
  243. currentTime = video.currentTime;//未检测到广告,记录播放进度
  244.  
  245. log(`记录当前进度:`)
  246. log(currentTime)
  247. },2000);//太快,进度条归零但广告节点却没有出来会判断错误
  248.  
  249. //监听点击进度条,主要是避免定时器对视频进度监听太慢导致进度条在跳转广告后出现偏移的情况
  250. progress = document.querySelector(`.ytp-progress-bar-container`);
  251. progress.addEventListener(`click`,clickProgressHandler);
  252.  
  253. }
  254.  
  255. //结束监听
  256. function closeObserve(){
  257. observer.disconnect();
  258. clearInterval(updateTimerId);
  259. progress.removeEventListener(`click`,clickProgressHandler);
  260. observer = null;
  261. updateTimerId = null;
  262. progress = null;
  263. }
  264.  
  265.  
  266. setInterval(function(){
  267. //视频播放页
  268. if(getUrlParams(`v`)){
  269. if(observer && updateTimerId && progress){
  270. return false;
  271. }
  272. startObserve();
  273. }else{
  274. //其它界面
  275. if(!observer && !updateTimerId && !progress){
  276. return false;
  277. }
  278. closeObserve();
  279. }
  280. },16.7);
  281.  
  282. log(`去除视频广告脚本持续运行中`)
  283. }
  284.  
  285. /**
  286. * main函数
  287. */
  288. function main(){
  289. generateRemoveADHTMLElement(generateRemoveADCssText(cssSeletorArr),`removeAD`);//移除界面中的广告.
  290. removePlayerAD();//移除播放中的广告.
  291. }
  292.  
  293. if (document.readyState === `loading`) {
  294. log(`YouTube去广告脚本即将调用:`);
  295. document.addEventListener(`DOMContentLoaded`, main);// 此时加载尚未完成
  296. } else {
  297. log(`YouTube去广告脚本快速调用:`);
  298. main();// 此时`DOMContentLoaded` 已经被触发
  299. }
  300.  
  301. })();