YouTube 自动跳过广告

自动跳过 YouTube 视频广告

目前为 2024-12-23 提交的版本。查看 最新版本

  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 6.1.5
  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. // @require https://update.greasyfork.org/scripts/519877/1497523/UserScript%20Compatibility%20Library.js
  34. // @run-at document-start
  35. // ==/UserScript==
  36.  
  37. (function () {
  38. 'use strict';
  39.  
  40. const CONFIG = {
  41. skipAds: GM_getValue('skipAds', true),
  42. hideAds: GM_getValue('hideAds', true),
  43. muteAds: GM_getValue('muteAds', true),
  44. bypassAdBlock: GM_getValue('bypassAdBlock', true),
  45. };
  46.  
  47. const saveConfig = () => {
  48. Object.keys(CONFIG).forEach(key => {
  49. GM_setValue(key, CONFIG[key]);
  50. });
  51. };
  52.  
  53. const AdDetector = {
  54. selectors: {
  55. skippable: ['.ytp-ad-skip-button', '.ytp-ad-skip-button-modern', '.videoAdUiSkipButton', '.ytp-ad-overlay-close-button', '.ytp-ad-text-overlay-skip-button', '.ytp-ad-skip-button-container', '.ytp-ad-preview-container'],
  56. hideable: ['.ad-showing', '.ytp-ad-module', 'ytd-ad-slot-renderer', '#masthead-ad', '.video-ads', '.ytp-ad-overlay-container', 'ytd-promoted-sparkles-web-renderer', 'ytd-promoted-video-renderer', '.ytd-watch-next-secondary-results-renderer.ytd-item-section-renderer', 'tp-yt-paper-dialog[role="dialog"]'],
  57. adBlockDetection: ['ytd-enforcement-message-view-model', '#error-page', '#dialog.ytd-popup-container'],
  58. },
  59.  
  60. isAdPlaying(video) {
  61. return document.querySelector('.ad-showing') !== null || (video?.src && video.src.includes('/ads/')) || document.querySelector('.ytp-ad-player-overlay') !== null;
  62. },
  63. };
  64.  
  65. class AdSkipper {
  66. constructor() {
  67. this.originalVolume = null;
  68. this.observer = null;
  69. this.retryCount = 0;
  70. this.maxRetries = 5;
  71. this.lastVideoState = null;
  72. }
  73.  
  74. initialize() {
  75. this.setupObservers();
  76. this.checkForAds();
  77. this.bypassAdBlockDetection();
  78. this.setupVideoStateTracking();
  79.  
  80. document.addEventListener('DOMContentLoaded', () => {
  81. this.applyCriticalStyles();
  82. });
  83.  
  84. setInterval(() => {
  85. this.checkForAds();
  86. this.bypassAdBlockDetection();
  87. }, 1000);
  88. }
  89.  
  90. setupVideoStateTracking() {
  91. document.addEventListener('visibilitychange', () => {
  92. const video = document.querySelector('video');
  93. if (video) {
  94. if (document.hidden) {
  95. this.lastVideoState = video.paused;
  96. } else if (this.lastVideoState !== null) {
  97. if (this.lastVideoState) {
  98. video.pause();
  99. } else {
  100. video.play().catch(() => {});
  101. }
  102. }
  103. }
  104. });
  105. }
  106.  
  107. applyCriticalStyles() {
  108. const styles = `
  109. .ad-showing video { display: none !important; }
  110. .video-ads { display: none !important; }
  111. .ytp-ad-overlay-container { display: none !important; }
  112. .ytp-ad-message-container { display: none !important; }
  113. ytd-promoted-sparkles-web-renderer { display: none !important; }
  114. ytd-promoted-video-renderer { display: none !important; }
  115. tp-yt-paper-dialog[role="dialog"] { display: none !important; }
  116. ytd-enforcement-message-view-model { display: none !important; }
  117. #error-page { display: none !important; }
  118. #dialog.ytd-popup-container { display: none !important; }
  119. `;
  120. GM_addStyle(styles);
  121. }
  122.  
  123. bypassAdBlockDetection() {
  124. if (!CONFIG.bypassAdBlock) return;
  125.  
  126. AdDetector.selectors.adBlockDetection.forEach(selector => {
  127. const elements = document.querySelectorAll(selector);
  128. elements.forEach(element => element?.remove());
  129. });
  130.  
  131. const video = document.querySelector('video');
  132. if (video?.paused && !document.hidden && this.lastVideoState === false) {
  133. video.play().catch(() => {});
  134. }
  135. }
  136.  
  137. async skipAd() {
  138. const video = document.querySelector('video');
  139. if (!video) return;
  140.  
  141. try {
  142. if ((await this.clickSkipButton()) || (await this.skipVideoAd(video))) {
  143. if (CONFIG.muteAds) {
  144. this.muteAd(video);
  145. }
  146. this.retryCount = 0;
  147. video.style.display = 'block';
  148. video.style.visibility = 'visible';
  149.  
  150. // Khôi phục trạng thái video sau khi bỏ qua quảng cáo
  151. if (this.lastVideoState) {
  152. video.pause();
  153. }
  154. } else if (this.retryCount < this.maxRetries) {
  155. this.retryCount++;
  156. setTimeout(() => this.skipAd(), 500);
  157. }
  158. } catch {
  159. // Bỏ qua lỗi nếu có
  160. }
  161. }
  162.  
  163. async clickSkipButton() {
  164. for (const selector of AdDetector.selectors.skippable) {
  165. const buttons = document.querySelectorAll(selector);
  166. for (const button of buttons) {
  167. if (button?.offsetParent !== null) {
  168. try {
  169. button.click();
  170. return true;
  171. } catch {
  172. continue;
  173. }
  174. }
  175. }
  176. }
  177. return false;
  178. }
  179.  
  180. async skipVideoAd(video) {
  181. if (AdDetector.isAdPlaying(video)) {
  182. try {
  183. video.currentTime = video.duration || 0;
  184. video.playbackRate = 16;
  185. if (video.paused && !document.hidden && this.lastVideoState === false) {
  186. await video.play().catch(() => {});
  187. }
  188. return true;
  189. } catch {
  190. return false;
  191. }
  192. }
  193. return false;
  194. }
  195.  
  196. muteAd(video) {
  197. if (this.originalVolume === null) {
  198. this.originalVolume = video.volume;
  199. }
  200. try {
  201. video.muted = true;
  202. video.volume = 0;
  203. const muteButton = document.querySelector('.ytp-mute-button');
  204. if (muteButton && !video.muted) {
  205. muteButton.click();
  206. }
  207. } catch {
  208. // Bỏ qua lỗi nếu có
  209. }
  210. }
  211.  
  212. setupObservers() {
  213. this.observer?.disconnect();
  214.  
  215. this.observer = new MutationObserver(() => {
  216. requestAnimationFrame(() => {
  217. this.checkForAds();
  218. this.bypassAdBlockDetection();
  219. });
  220. });
  221.  
  222. this.observer.observe(document.body, {
  223. childList: true,
  224. subtree: true,
  225. attributes: true,
  226. attributeFilter: ['class', 'src', 'style'],
  227. });
  228. }
  229.  
  230. checkForAds() {
  231. if (CONFIG.skipAds) {
  232. this.skipAd();
  233. }
  234.  
  235. if (CONFIG.hideAds) {
  236. this.hideAds();
  237. }
  238. }
  239.  
  240. hideAds() {
  241. AdDetector.selectors.hideable.forEach(selector => {
  242. document.querySelectorAll(selector).forEach(el => {
  243. try {
  244. el?.parentNode && el.remove();
  245. } catch {
  246. // Bỏ qua lỗi nếu có
  247. }
  248. });
  249. });
  250. }
  251.  
  252. destroy() {
  253. if (this.observer) {
  254. this.observer.disconnect();
  255. this.observer = null;
  256. }
  257. saveConfig();
  258. }
  259. }
  260.  
  261. const init = () => {
  262. try {
  263. const adSkipper = new AdSkipper();
  264. adSkipper.initialize();
  265.  
  266. window.addEventListener('unload', () => {
  267. adSkipper.destroy();
  268. });
  269. } catch {
  270. setTimeout(init, 1000);
  271. }
  272. };
  273.  
  274. if (document.readyState === 'loading') {
  275. document.addEventListener('DOMContentLoaded', init);
  276. } else {
  277. init();
  278. }
  279. })();