Auto Skip YouTube Ads @ Gurveer

Automatically skip YouTube ads instantly with minimal detection risk. Features configurable settings, update notifications, robust error handling, and fixes for mute state restoration.

  1. // ==UserScript==
  2. // @name Auto Skip YouTube Ads @ Gurveer
  3. // @name:ar تخطي إعلانات YouTube تلقائيًا @ Gurveer
  4. // @name:bg Пропускане на YouTube-реклами
  5. // @name:es Saltar Automáticamente Anuncios De YouTube @ Gurveer
  6. // @name:fr Ignorer Automatiquement Les Publicités YouTube
  7. // @name:hi YouTube विज्ञापन स्वचालित रूप से छोड़ें
  8. // @name:id Lewati Otomatis Iklan YouTube
  9. // @name:ja YouTube 広告を自動スキップ
  10. // @name:ko YouTube 광고 자동 건너뛰기
  11. // @name:nl YouTube-Advertenties Automatisch Overslaan
  12. // @name:pt-BR Pular Automaticamente Anúncios Do YouTube
  13. // @name:ru Автоматический Пропуск Рекламы На YouTube
  14. // @name:vi Tự Động Bỏ Qua Quảng Cáo YouTube
  15. // @name:zh-CN 自动跳过 YouTube 广告
  16. // @name:zh-TW 自動跳過 YouTube 廣告
  17. // @namespace https://github.com/gurr-i/browser-scripts
  18. // @version 8.0.1
  19. // @description Automatically skip YouTube ads instantly with minimal detection risk. Features configurable settings, update notifications, robust error handling, and fixes for mute state restoration.
  20. // @description:bg Automatically skip YouTube ads instantly with minimal detection risk. Features configurable settings, update notifications, robust error handling, and fixes for mute state restoration.
  21. // @description:en Automatically skip YouTube ads instantly with minimal detection risk. Features configurable settings, update notifications, robust error handling, and fixes for mute state restoration.
  22. // @description:ar تخطي إعلانات YouTube تلقائيًا على الفور مع الحد الأدنى من مخاطر الكشف. يتضمن إعدادات قابلة للتخصيص، إشعارات التحديث، ومعالجة قوية للأخطاء، وإصلاحات لاستعادة حالة كتم الصوت.
  23. // @description:es Omite automáticamente los anuncios de YouTube al instante con un riesgo mínimo de detección. Incluye configuraciones personalizables, notificaciones de actualización, manejo robusto de errores y correcciones para la restauración del estado de silencio.
  24. // @description:fr Ignorez automatiquement et instantanément les publicités YouTube avec un risque minimal de détection. Comprend des paramètres configurables, des notifications de mise à jour, une gestion robuste des erreurs et des corrections pour la restauration de l'état muet.
  25. // @description:hi YouTube विज्ञापनों को तुरंत स्वचालित रूप से छोड़ दें, जिसमें न्यूनतम पता लगाने का जोखिम हो। इसमें कॉन्फ़िगर करने योग्य सेटिंग्स, अपडेट सूचनाएं, मजबूत त्रुटि हैंडलिंग और म्यूट स्थिति बहाली के लिए सुधार शामिल हैं।
  26. // @description:id Lewati iklan YouTube secara otomatis secara instan dengan risiko deteksi minimal. Termasuk pengaturan yang dapat dikonfigurasi, pemberitahuan pembaruan, penanganan kesalahan yang kuat, dan perbaikan untuk pemulihan status bisu.
  27. // @description:ja YouTube 広告を即座に自動的にスキップし、検出リスクを最小限に抑えます。カスタマイズ可能な設定、更新通知、堅牢なエラーハンドリング、ミュート状態の復元修正を備えています。
  28. // @description:ko YouTube 광고를 즉시 자동으로 건너뛰며 탐지 위험이 최소화됩니다。구성 가능한 설정, 업데이트 알림, 강력한 오류 처리 및 음소거 상태 복원 수정이 포함됩니다.
  29. // @description:nl Sla YouTube-advertenties direct automatisch over met minimaal detectierisico. Bevat configureerbare instellingen, updatemeldingen, robuuste foutafhandeling en fixes voor het herstellen van de mute-status.
  30. // @description:pt-BR Pule anúncios do YouTube instantaneamente com risco mínimo de detecção. Inclui configurações personalizáveis, notificações de atualização, tratamento robusto de erros e correções para restauração do estado de mudo.
  31. // @description:ru Автоматически пропускать рекламу YouTube мгновенно с минимальным риском обнаружения. Включает настраиваемые параметры, уведомления об обновлениях, надежную обработку ошибок и исправления для восстановления состояния звука.
  32. // @description:vi Tự động bỏ qua quảng cáo YouTube ngay lập tức với rủi ro phát hiện tối thiểu. Bao gồm cài đặt có thể tùy chỉnh, thông báo cập nhật, xử lý lỗi mạnh mẽ và sửa lỗi cho việc khôi phục trạng thái tắt tiếng.
  33. // @description:zh-CN 立即自动跳过 YouTube 广告,检测风险最小。包括可配置设置、更新通知、强大的错误处理和修复静音状态恢复。
  34. // @description:zh-TW 立即自動跳過 YouTube 廣告,偵測風險極低。包括可配置設定、更新通知、強大的錯誤處理和修復靜音狀態恢復。
  35. // @author Gurveer
  36. // @icon https://raw.githubusercontent.com/gurr-i/browser-scripts/main/assets/icons/youtube-ads-skipper.png
  37. // @match https://www.youtube.com/*
  38. // @match https://m.youtube.com/*
  39. // @match https://music.youtube.com/*
  40. // @exclude https://studio.youtube.com/*
  41. // @grant none
  42. // @license MIT
  43. // @compatible firefox
  44. // @compatible chrome
  45. // @compatible opera
  46. // @compatible safari
  47. // @compatible edge
  48. // @noframes
  49. // @homepage https://github.com/gurr-i/browser-scripts/tree/main/scripts/Auto-Skip-YouTube-Ads
  50. // ==/UserScript==
  51.  
  52. (function () {
  53. 'use strict';
  54. // Configuration settings with UI controls and selectors
  55. const defaultConfig = {
  56. debug: false,
  57. updateCheckInterval: 24 * 60 * 60 * 1000,
  58. maxRetries: 3,
  59. retryDelay: 1000,
  60. randomizationDelay: 200,
  61. aggressiveAdRemoval: true,
  62. skipOverlayAds: true,
  63. skipVideoAds: true,
  64. muteAds: true,
  65. autoUpdateCheck: true,
  66. selectors: {
  67. adShowing: '.ad-showing, .ytp-ad-player-overlay',
  68. pieCountdown: '.ytp-ad-timed-pie-countdown-container, .ytp-ad-duration-remaining',
  69. surveyQuestions: '.ytp-ad-survey-questions, .ytp-ad-survey-interstitial',
  70. player: '#ytd-player, #movie_player',
  71. mobilePlayer: '#movie_player, #player-container-id',
  72. adVideo: '#ytd-player video.html5-main-video, #song-video video.html5-main-video, .video-stream.html5-main-video',
  73. skipButton: '.ytp-ad-skip-button, .ytp-ad-skip-button-modern',
  74. muteButton: '.ytp-mute-button',
  75. ads: [
  76. '#player-ads',
  77. '#panels > ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
  78. '#masthead-ad',
  79. '.yt-mealbar-promo-renderer',
  80. '.ytp-ad-overlay-container',
  81. '.ytp-ad-overlay-slot',
  82. '.ytp-ad-text-overlay',
  83. '.ytp-ad-preview-text-overlay',
  84. '.ytp-ad-preview-container',
  85. '.video-ads.ytp-ad-module',
  86. '.ytp-featured-product',
  87. 'ytd-merch-shelf-renderer',
  88. 'ytmusic-mealbar-promo-renderer',
  89. 'ytmusic-statement-banner-renderer',
  90. '.ytd-promoted-sparkles-web-renderer',
  91. '.ytd-display-ad-renderer',
  92. '.ytd-statement-banner-renderer',
  93. '.ytd-in-feed-ad-layout-renderer',
  94. ],
  95. adElements: [
  96. ['ytd-reel-video-renderer', '.ytd-ad-slot-renderer'],
  97. ['tp-yt-paper-dialog', '#feedback.ytd-enforcement-message-view-model'],
  98. ['tp-yt-paper-dialog', ':scope > ytd-checkbox-survey-renderer'],
  99. ['tp-yt-paper-dialog', ':scope > ytd-single-option-survey-renderer'],
  100. ],
  101. },
  102. githubRepo: 'https://github.com/gurr-i/browser-scripts',
  103. };
  104. // Load saved configuration or use defaults
  105. const config = {
  106. ...defaultConfig,
  107. ...JSON.parse(localStorage.getItem('AutoSkipYouTubeAds_Config') || '{}'),
  108. };
  109. // UI Module for settings
  110. const settingsUI = {
  111. createSettingsPanel() {
  112. const panel = document.createElement('div');
  113. panel.className = 'auto-skip-settings';
  114. panel.style.cssText =
  115. 'position:fixed;top:10px;right:10px;background:#fff;padding:10px;border-radius:5px;box-shadow:0 2px 10px rgba(0,0,0,0.1);z-index:9999;display:none;';
  116. const title = document.createElement('h3');
  117. title.textContent = 'Auto Skip Settings';
  118. title.style.margin = '0 0 10px';
  119. panel.appendChild(title);
  120. const settings = [
  121. { key: 'debug', label: 'Debug Mode', type: 'checkbox' },
  122. { key: 'skipOverlayAds', label: 'Skip Overlay Ads', type: 'checkbox' },
  123. { key: 'skipVideoAds', label: 'Skip Video Ads', type: 'checkbox' },
  124. { key: 'muteAds', label: 'Mute Ads', type: 'checkbox' },
  125. { key: 'aggressiveAdRemoval', label: 'Aggressive Ad Removal', type: 'checkbox' },
  126. { key: 'autoUpdateCheck', label: 'Auto Update Check', type: 'checkbox' },
  127. ];
  128. settings.forEach((setting) => {
  129. const container = document.createElement('div');
  130. container.style.marginBottom = '5px';
  131. const input = document.createElement('input');
  132. input.type = setting.type;
  133. input.id = `auto-skip-${setting.key}`;
  134. input.checked = config[setting.key];
  135. input.addEventListener('change', () => {
  136. config[setting.key] = input.checked;
  137. localStorage.setItem('AutoSkipYouTubeAds_Config', JSON.stringify(config));
  138. });
  139. const label = document.createElement('label');
  140. label.htmlFor = input.id;
  141. label.textContent = setting.label;
  142. label.style.marginLeft = '5px';
  143. container.appendChild(input);
  144. container.appendChild(label);
  145. panel.appendChild(container);
  146. });
  147. document.body.appendChild(panel);
  148. return panel;
  149. },
  150. init() {
  151. const panel = this.createSettingsPanel();
  152. const toggleBtn = document.createElement('button');
  153. toggleBtn.textContent = '⚙️';
  154. toggleBtn.style.cssText =
  155. 'position:fixed;top:10px;right:10px;z-index:9999;background:none;border:none;font-size:20px;cursor:pointer;';
  156. toggleBtn.addEventListener('click', () => {
  157. panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
  158. });
  159. document.body.appendChild(toggleBtn);
  160. },
  161. };
  162. // Environment detection
  163. const isYouTubeMobile = location.hostname === 'm.youtube.com';
  164. const isYouTubeMusic = location.hostname === 'music.youtube.com';
  165. const isYouTubeVideo = !isYouTubeMusic;
  166. // Utility module
  167. const utils = {
  168. log(...args) {
  169. if (config.debug) console.log('[AutoSkipYouTubeAds]', ...args);
  170. },
  171. getCurrentTimeString() {
  172. return new Date().toTimeString().split(' ', 1)[0];
  173. },
  174. checkIsYouTubeShorts() {
  175. return location.pathname.startsWith('/shorts/');
  176. },
  177. randomDelay(max = config.randomizationDelay) {
  178. return Math.random() * max;
  179. },
  180. async sleep(ms) {
  181. return new Promise((resolve) => setTimeout(resolve, ms));
  182. },
  183. };
  184. // Ad hiding module
  185. const adHider = {
  186. addCss() {
  187. const validSelectors = config.selectors.ads.filter((selector) =>
  188. /^[a-zA-Z0-9\-#.>[\]=:,\s]+$/.test(selector)
  189. );
  190. if (!validSelectors.length) return;
  191. const css = `${validSelectors.join(',')} { display: none !important; }`;
  192. const style = document.createElement('style');
  193. style.textContent = css;
  194. document.head.appendChild(style);
  195. utils.log('CSS styles applied for ad hiding');
  196. },
  197. removeAdElements() {
  198. try {
  199. if (!config.aggressiveAdRemoval) return;
  200. for (const [parentSelector, childSelector] of config.selectors.adElements) {
  201. const parent = document.querySelector(parentSelector);
  202. if (!parent) continue;
  203. const child = parent.querySelector(childSelector);
  204. if (!child) continue;
  205. parent.remove();
  206. utils.log(`Removed ad element: ${parentSelector} > ${childSelector}`);
  207. }
  208. } catch (error) {
  209. console.error('Error removing ad elements:', error);
  210. }
  211. },
  212. };
  213. // Ad skipping module with enhanced mute state handling
  214. const adSkipper = {
  215. wasVideoMuted: null, // Store original mute state (null if not set)
  216. async restoreMuteState(player) {
  217. try {
  218. if (this.wasVideoMuted === null) return; // No mute state to restore
  219. const isMuted = player.isMuted?.() ?? false;
  220. if (isMuted !== this.wasVideoMuted) {
  221. if (this.wasVideoMuted) {
  222. player.mute?.();
  223. } else {
  224. player.unMute?.();
  225. }
  226. utils.log(`Restored mute state to: ${this.wasVideoMuted ? 'muted' : 'unmuted'}`);
  227. }
  228. } catch (error) {
  229. console.error('Error restoring mute state:', error);
  230. }
  231. },
  232. async skipAd(retryCount = 0) {
  233. try {
  234. if (utils.checkIsYouTubeShorts()) return;
  235. const adShowing = document.querySelector(config.selectors.adShowing);
  236. const pieCountdown = document.querySelector(config.selectors.pieCountdown);
  237. const surveyQuestions = document.querySelector(config.selectors.surveyQuestions);
  238. const skipButton = document.querySelector(config.selectors.skipButton);
  239. const playerSelector = isYouTubeMobile || isYouTubeMusic ? config.selectors.mobilePlayer : config.selectors.player;
  240. const playerEl = document.querySelector(playerSelector);
  241. const player = isYouTubeMobile || isYouTubeMusic ? playerEl : playerEl?.getPlayer?.();
  242. if (!playerEl || !player) {
  243. utils.log({ message: 'Player not found', timeStamp: utils.getCurrentTimeString() });
  244. if (retryCount < config.maxRetries) {
  245. await utils.sleep(config.retryDelay);
  246. return this.skipAd(retryCount + 1);
  247. }
  248. return;
  249. }
  250. // Check if no ad is showing and restore mute state
  251. if (!adShowing && !pieCountdown && !surveyQuestions && !skipButton) {
  252. if (config.muteAds) {
  253. await this.restoreMuteState(player);
  254. }
  255. return;
  256. }
  257. // Try to click skip button first if available
  258. if (skipButton && config.skipVideoAds) {
  259. skipButton.click();
  260. utils.log({ message: 'Skip button clicked', timeStamp: utils.getCurrentTimeString() });
  261. // Schedule mute state restoration after skip
  262. if (config.muteAds) {
  263. await utils.sleep(500); // Wait for video to load
  264. await this.restoreMuteState(player);
  265. }
  266. return;
  267. }
  268. // Store original mute state before muting ads
  269. if (config.muteAds && this.wasVideoMuted === null) {
  270. this.wasVideoMuted = player.isMuted?.() ?? false;
  271. utils.log(`Stored original mute state: ${this.wasVideoMuted ? 'muted' : 'unmuted'}`);
  272. }
  273. // Mute ads if enabled
  274. if (config.muteAds && !player.isMuted?.()) {
  275. player.mute?.();
  276. utils.log('Ad muted');
  277. }
  278. let adVideo = null;
  279. if (!pieCountdown && !surveyQuestions) {
  280. adVideo = document.querySelector(config.selectors.adVideo);
  281. if (!adVideo || !adVideo.src || adVideo.paused || isNaN(adVideo.duration)) {
  282. utils.log('Invalid ad video state');
  283. return;
  284. }
  285. utils.log({ message: 'Ad video detected', timeStamp: utils.getCurrentTimeString() });
  286. }
  287. // Simulate user-like behavior with random delay
  288. await utils.sleep(utils.randomDelay());
  289. if (isYouTubeMusic && adVideo) {
  290. adVideo.currentTime = adVideo.duration;
  291. utils.log({
  292. message: 'Ad skipped (YouTube Music)',
  293. timeStamp: utils.getCurrentTimeString(),
  294. adShowing: !!adShowing,
  295. pieCountdown: !!pieCountdown,
  296. surveyQuestions: !!surveyQuestions,
  297. });
  298. // Schedule mute state restoration
  299. if (config.muteAds) {
  300. await utils.sleep(500); // Wait for video to load
  301. await this.restoreMuteState(player);
  302. }
  303. } else {
  304. const videoData = player.getVideoData?.() || {};
  305. const videoId = videoData.video_id;
  306. if (!videoId) {
  307. utils.log('Video ID not found');
  308. if (retryCount < config.maxRetries) {
  309. await utils.sleep(config.retryDelay);
  310. return this.skipAd(retryCount + 1);
  311. }
  312. return;
  313. }
  314. const start = Math.floor(player.getCurrentTime?.() || 0);
  315. const loadMethod = playerEl.loadVideoWithPlayerVars ? 'loadVideoWithPlayerVars' : 'loadVideoByPlayerVars';
  316. playerEl[loadMethod]?.({ videoId, start });
  317. utils.log({
  318. message: 'Ad skipped',
  319. videoId,
  320. start,
  321. title: videoData.title || 'Unknown',
  322. timeStamp: utils.getCurrentTimeString(),
  323. adShowing: !!adShowing,
  324. pieCountdown: !!pieCountdown,
  325. surveyQuestions: !!surveyQuestions,
  326. });
  327. // Schedule mute state restoration
  328. if (config.muteAds) {
  329. await utils.sleep(500); // Wait for video to load
  330. await this.restoreMuteState(player);
  331. }
  332. }
  333. } catch (error) {
  334. console.error('Error in skipAd:', error);
  335. if (retryCount < config.maxRetries) {
  336. await utils.sleep(config.retryDelay);
  337. return this.skipAd(retryCount + 1);
  338. }
  339. }
  340. },
  341. setupObserver() {
  342. const observer = new MutationObserver((mutations) => {
  343. if (
  344. mutations.some(
  345. (mutation) =>
  346. mutation.target.matches(config.selectors.adShowing) ||
  347. mutation.target.querySelector(config.selectors.adShowing)
  348. )
  349. ) {
  350. this.skipAd();
  351. }
  352. });
  353. observer.observe(document.body, {
  354. attributes: true,
  355. attributeFilter: ['class'],
  356. childList: true,
  357. subtree: true,
  358. });
  359. utils.log('MutationObserver initialized');
  360. return observer;
  361. },
  362. };
  363. // Update checker module
  364. const updater = {
  365. async checkForUpdates() {
  366. try {
  367. const lastCheck = localStorage.getItem('AutoSkipYouTubeAds_LastUpdateCheck');
  368. const now = Date.now();
  369. if (lastCheck && now - parseInt(lastCheck) < config.updateCheckInterval) return;
  370. const response = await fetch('https://api.github.com/repos/gurr-i/browser-scripts/releases/latest', {
  371. headers: { Accept: 'application/vnd.github.v3+json' },
  372. });
  373. const data = await response.json();
  374. if (data.tag_name && data.tag_name > '8.0.1') {
  375. const message = `Auto Skip YouTube Ads: New version ${data.tag_name} available! Update at ${config.githubRepo}`;
  376. console.warn(message);
  377. if (Notification.permission === 'granted') {
  378. new Notification(message);
  379. } else if (Notification.permission !== 'denied') {
  380. Notification.requestPermission().then((permission) => {
  381. if (permission === 'granted') new Notification(message);
  382. });
  383. }
  384. }
  385. localStorage.setItem('AutoSkipYouTubeAds_LastUpdateCheck', now.toString());
  386. } catch (error) {
  387. utils.log('Update check failed:', error);
  388. }
  389. },
  390. };
  391. // Initialize script
  392. function init() {
  393. settingsUI.init();
  394. adHider.addCss();
  395. if (isYouTubeVideo && config.aggressiveAdRemoval) {
  396. adHider.removeAdElements();
  397. adSkipper.setupObserver();
  398. }
  399. if (config.skipVideoAds || config.skipOverlayAds) {
  400. adSkipper.skipAd();
  401. }
  402. if (config.autoUpdateCheck) {
  403. updater.checkForUpdates();
  404. }
  405. utils.log('Auto Skip YouTube Ads initialized with settings:', config);
  406. }
  407. // Run initialization
  408. init();
  409. })();