YouTube 自動跳過廣告

自動跳過 YouTube 影片廣告

目前為 2024-12-31 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name YouTube Bỏ qua quảng cáo video tự động
  3. // @name:en YouTube Auto Ad Skipper
  4. // @name:vi YouTube Bỏ qua quảng cáo video tự động
  5. // @name:zh-cn YouTube 自动跳过广告
  6. // @name:zh-tw YouTube 自動跳過廣告
  7. // @name:ja YouTube 広告自動スキップ
  8. // @name:ko YouTube 자동 광고 건너뛰기
  9. // @name:es YouTube Saltar anuncios automáticamente
  10. // @name:ru YouTube Автоматический пропуск рекламы
  11. // @name:id YouTube Lewati Iklan Otomatis
  12. // @name:hi YouTube स्वचालित विज्ञापन स्किपर
  13. // @namespace http://tampermonkey.net/
  14. // @version 2024.12.31.1
  15. // @description Tự động bỏ qua quảng cáo trên YouTube
  16. // @description:en Automatically skip ads on YouTube videos
  17. // @description:vi Tự động bỏ qua quảng cáo trên YouTube
  18. // @description:zh-cn 自动跳过 YouTube 视频广告
  19. // @description:zh-tw 自動跳過 YouTube 影片廣告
  20. // @description:ja YouTube動画の広告を自動的にスキップ
  21. // @description:ko YouTube 동영상의 광고를 자동으로 건너뛰기
  22. // @description:es Salta automáticamente los anuncios en videos de YouTube
  23. // @description:ru Автоматически пропускает рекламу в видео на YouTube
  24. // @description:id Otomatis melewati iklan di video YouTube
  25. // @description:hi YouTube वीडियो में विज्ञापनों को स्वचालित रूप से छोड़ें
  26. // @author RenjiYuusei
  27. // @license MIT
  28. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  29. // @match https://*.youtube.com/*
  30. // @grant GM_addStyle
  31. // @grant GM_getValue
  32. // @grant GM_setValue
  33. // @run-at document-start
  34. // @compatible chrome
  35. // @compatible firefox
  36. // @compatible edge
  37. // @compatible safari
  38. // ==/UserScript==
  39.  
  40. const DEBUG = false;
  41.  
  42. function log(...args) {
  43. if (DEBUG) {
  44. console.log('[YouTube Ad Skipper]:', ...args);
  45. }
  46. }
  47.  
  48. (function () {
  49. 'use strict';
  50.  
  51. const DEFAULT_CONFIG = {
  52. allowedReloadPage: true,
  53. dontReloadWhileBusy: true,
  54. maxScrollThreshold: 200,
  55. adSkipDelay: 300,
  56. maxPlaybackRate: 16,
  57. maxSkipAttempts: 15,
  58. autoMuteAds: true,
  59. hideAllAds: true,
  60. checkInterval: 500,
  61. minSkipInterval: 50,
  62. };
  63.  
  64. class YouTubeAdSkipper {
  65. constructor() {
  66. this.video = null;
  67. this.currentVideoTime = 0;
  68. this.isTabBlurred = false;
  69. this.skipAttempts = 0;
  70. this.maxSkipAttempts = DEFAULT_CONFIG.maxSkipAttempts;
  71. this.lastSkipTime = 0;
  72. this.config = DEFAULT_CONFIG;
  73. this.errorCount = 0;
  74. this.maxErrors = 3;
  75. this.debounceTimeout = null;
  76. this.init();
  77. }
  78.  
  79. init() {
  80. try {
  81. this.loadConfig();
  82. this.setupEventListeners();
  83. this.setupMutationObserver();
  84.  
  85. if (this.config.hideAllAds) {
  86. this.addCSSHideAds();
  87. }
  88.  
  89. this.skipAd();
  90. this.startAdCheckInterval();
  91. } catch (error) {
  92. log('Error during initialization:', error);
  93. }
  94. }
  95.  
  96. loadConfig() {
  97. try {
  98. const savedConfig = GM_getValue('adSkipperConfig');
  99. if (savedConfig) {
  100. this.config = { ...DEFAULT_CONFIG, ...savedConfig };
  101. }
  102. } catch (error) {
  103. log('Error when read config, restore to default:', error);
  104. this.config = DEFAULT_CONFIG;
  105. this.saveConfig();
  106. }
  107. }
  108.  
  109. saveConfig() {
  110. try {
  111. GM_setValue('adSkipperConfig', this.config);
  112. } catch (error) {
  113. log('Error when save config:', error);
  114. }
  115. }
  116.  
  117. setupEventListeners() {
  118. window.addEventListener('blur', () => (this.isTabBlurred = true));
  119. window.addEventListener('focus', () => {
  120. this.isTabBlurred = false;
  121. this.skipAd();
  122. });
  123.  
  124. document.addEventListener('timeupdate', this.handleTimeUpdate.bind(this), true);
  125.  
  126. document.addEventListener('yt-navigate-finish', () => {
  127. this.skipAttempts = 0;
  128. this.skipAd();
  129. });
  130.  
  131. document.addEventListener(
  132. 'pause',
  133. () => {
  134. if (this.video && this.video.paused) {
  135. log('Video is paused, try to play...');
  136. setTimeout(() => {
  137. this.video.play().catch(error => {
  138. log('Cannot play video automatically:', error);
  139. });
  140. }, 500);
  141. }
  142. },
  143. true
  144. );
  145. }
  146.  
  147. handleTimeUpdate(e) {
  148. if (e.target.matches('video.html5-main-video')) {
  149. this.currentVideoTime = e.target.currentTime;
  150. }
  151. }
  152.  
  153. setupMutationObserver() {
  154. const observer = new MutationObserver(() => {
  155. if (this.isTabBlurred) return;
  156.  
  157. clearTimeout(this.debounceTimeout);
  158. this.debounceTimeout = setTimeout(() => {
  159. this.skipAd();
  160. }, 100);
  161. });
  162.  
  163. // Wait until document.body exists
  164. const observeBody = () => {
  165. if (document.body) {
  166. observer.observe(document.body, {
  167. attributes: true,
  168. attributeFilter: ['class', 'src', 'style'],
  169. childList: true,
  170. subtree: true,
  171. });
  172. } else {
  173. // Try again after 50ms if body does not exist
  174. setTimeout(observeBody, 50);
  175. }
  176. };
  177.  
  178. observeBody();
  179. }
  180.  
  181. startAdCheckInterval() {
  182. setInterval(() => {
  183. if (!this.isTabBlurred) {
  184. this.skipAd();
  185. }
  186. }, this.config.checkInterval);
  187. }
  188.  
  189. async skipAd() {
  190. try {
  191. if (window.location.pathname.startsWith('/shorts/')) return;
  192.  
  193. const player = document.querySelector('#movie_player');
  194. if (!player) {
  195. log('Not found player');
  196. return;
  197. }
  198.  
  199. const hasAd = player.classList.contains('ad-showing');
  200. this.video = player.querySelector('video.html5-main-video');
  201.  
  202. if (hasAd && this.video) {
  203. await this.handleVideoAd();
  204. }
  205.  
  206. this.removeAdBlockerWarnings();
  207. this.removeShortVideoAds();
  208. this.removeOverlayAds();
  209. } catch (error) {
  210. this.errorCount++;
  211. log('Error when skip ads:', error);
  212. if (this.errorCount >= this.maxErrors) {
  213. log('Exceeded the maximum number of retry attempts');
  214. }
  215. }
  216. }
  217.  
  218. async handleVideoAd() {
  219. const now = Date.now();
  220. if (now - this.lastSkipTime < this.config.minSkipInterval) return;
  221. this.lastSkipTime = now;
  222.  
  223. this.clickSkipButtons();
  224.  
  225. if (this.video.src) {
  226. this.video.currentTime = 9999;
  227. this.video.playbackRate = this.config.maxPlaybackRate;
  228. }
  229.  
  230. if (this.config.autoMuteAds) {
  231. this.video.muted = true;
  232. this.video.volume = 0;
  233. }
  234.  
  235. if (this.skipAttempts < this.maxSkipAttempts) {
  236. this.skipAttempts++;
  237. await new Promise(resolve => setTimeout(resolve, this.config.adSkipDelay));
  238. this.skipAd();
  239. }
  240. }
  241.  
  242. clickSkipButtons() {
  243. const skipButtonSelectors = ['.ytp-skip-ad-button', '.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.ytp-ad-survey-answer-button', '.ytp-ad-skip-button-container button'];
  244.  
  245. skipButtonSelectors.forEach(selector => {
  246. const buttons = document.querySelectorAll(selector);
  247. buttons.forEach(button => {
  248. if (button && button.offsetParent !== null) {
  249. button.click();
  250. button.remove();
  251. }
  252. });
  253. });
  254. }
  255.  
  256. removeAdBlockerWarnings() {
  257. const warningSelectors = ['tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model)', '.yt-playability-error-supported-renderers:has(.ytd-enforcement-message-view-model)', 'ytd-enforcement-message-view-model', '.ytd-popup-container'];
  258.  
  259. warningSelectors.forEach(selector => {
  260. const warning = document.querySelector(selector);
  261. if (warning) {
  262. if (selector.includes('playability-error') && this.checkCanReloadPage()) {
  263. this.reloadPage();
  264. }
  265. warning.remove();
  266. }
  267. });
  268. }
  269.  
  270. removeShortVideoAds() {
  271. const shortAdSelectors = ['ytd-reel-video-renderer:has(.ytd-ad-slot-renderer)', 'ytd-in-feed-ad-layout-renderer', 'ytd-promoted-video-renderer'].join(',');
  272.  
  273. document.querySelectorAll(shortAdSelectors).forEach(ad => ad.remove());
  274. }
  275.  
  276. removeOverlayAds() {
  277. const overlayAdSelectors = ['.ytp-ad-overlay-container', '.ytp-ad-overlay-slot', '.ytp-ad-text-overlay'].join(',');
  278.  
  279. document.querySelectorAll(overlayAdSelectors).forEach(ad => (ad.style.display = 'none'));
  280. }
  281.  
  282. checkCanReloadPage() {
  283. if (!this.config.allowedReloadPage) return false;
  284. if (!this.config.dontReloadWhileBusy) return true;
  285. if (document.activeElement?.matches('input, textarea, select')) return false;
  286. if (document.documentElement.scrollTop > this.config.maxScrollThreshold) return false;
  287. if (this.isTabBlurred) return false;
  288. return true;
  289. }
  290.  
  291. reloadPage() {
  292. const params = new URLSearchParams(location.search);
  293. if (this.currentVideoTime > 0) {
  294. params.set('t', Math.floor(this.currentVideoTime) + 's');
  295. }
  296. location.replace(`${location.origin}${location.pathname}?${params.toString()}`);
  297. }
  298.  
  299. addCSSHideAds() {
  300. const styles = `
  301. #player-ads,
  302. #masthead-ad,
  303. ytd-ad-slot-renderer,
  304. ytd-rich-item-renderer:has(.ytd-ad-slot-renderer),
  305. ytd-rich-section-renderer:has(.ytd-statement-banner-renderer),
  306. ytd-reel-video-renderer:has(.ytd-ad-slot-renderer),
  307. tp-yt-paper-dialog:has(#feedback.ytd-enforcement-message-view-model),
  308. tp-yt-paper-dialog:has(> ytd-checkbox-survey-renderer),
  309. .ytp-suggested-action,
  310. .yt-mealbar-promo-renderer,
  311. ytmusic-mealbar-promo-renderer,
  312. ytmusic-statement-banner-renderer,
  313. .ytd-display-ad-renderer,
  314. .ytd-statement-banner-renderer,
  315. .ytd-in-feed-ad-layout-renderer,
  316. .ytp-ad-overlay-container,
  317. .ytp-ad-text-overlay,
  318. ytd-promoted-sparkles-web-renderer,
  319. ytd-promoted-video-renderer,
  320. .ytd-banner-promo-renderer,
  321. .ytd-video-masthead-ad-v3-renderer,
  322. .ytd-primetime-promo-renderer,
  323. .ytp-ad-skip-button-slot,
  324. .ytp-ad-preview-slot,
  325. .ytp-ad-message-slot {
  326. display: none !important;
  327. }
  328. `;
  329. GM_addStyle(styles);
  330. }
  331. }
  332.  
  333. new YouTubeAdSkipper();
  334. })();

QingJ © 2025

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