AdGuard script block YouTube ads

Block Ads YouTube use AdGuard script

  1. // ==UserScript==
  2. // @name AdGuard script block YouTube ads
  3. // @version 0.0.1
  4. // @match *://*.youtube.com/*
  5. // @grant none
  6. // @noframes
  7. // @run-at document-idle
  8. // @namespace https://gf.qytechs.cn/users/848349
  9. // @description Block Ads YouTube use AdGuard script
  10. // ==/UserScript==
  11.  
  12.  
  13. /**
  14. * This file is part of AdGuard's Block YouTube Ads (https://github.com/AdguardTeam/BlockYouTubeAdsShortcut).
  15. *
  16. * AdGuard's Block YouTube Ads is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU General Public License as published by
  18. * the Free Software Foundation, either version 3 of the License, or
  19. * (at your option) any later version.
  20. *
  21. * AdGuard's Block YouTube Ads is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU General Public License
  27. * along with AdGuard's Block YouTube Ads. If not, see <http://www.gnu.org/licenses/>.
  28. */
  29.  
  30. /* global Response, window, navigator, document, MutationObserver, completion */
  31.  
  32. /**
  33. * The function that implements all the logic.
  34. * Returns the run status.
  35. */
  36. function runBlockYoutube() {
  37. const locales = {
  38. en: {
  39. logo: 'with&nbsp;AdGuard',
  40. alreadyExecuted: 'The shortcut has already been executed.',
  41. wrongDomain: 'This shortcut is supposed to be launched only on YouTube.',
  42. success: 'YouTube is now ad-free! Please note that you need to run this shortcut again if you reload the page.',
  43. },
  44. ru: {
  45. logo: 'с&nbsp;AdGuard',
  46. alreadyExecuted: 'Быстрая команда уже выполнена.',
  47. wrongDomain: 'Эта быстрая команда предназначена для использования только на YouTube.',
  48. success: 'Теперь YouTube без рекламы! Важно: при перезагрузке страницы вам нужно будет заново запустить команду.',
  49. },
  50. es: {
  51. logo: 'con&nbsp;AdGuard',
  52. alreadyExecuted: 'El atajo ya ha sido ejecutado.',
  53. wrongDomain: 'Se supone que este atajo se lanza sólo en YouTube.',
  54. success: '¡YouTube está ahora libre de anuncios! Ten en cuenta que tienes que volver a ejecutar este atajo si recargas la página.',
  55. },
  56. de: {
  57. logo: 'mit&nbsp;AdGuard',
  58. alreadyExecuted: 'Der Kurzbefehl wurde bereits ausgeführt.',
  59. wrongDomain: 'Dieser Kurzbefehl soll nur auf YouTube gestartet werden.',
  60. success: 'YouTube ist jetzt werbefrei! Bitte beachten Sie, dass Sie diesen Kurzbefehl erneut ausführen müssen, wenn Sie die Seite neu laden.',
  61. },
  62. fr: {
  63. logo: 'avec&nbsp;AdGuard',
  64. alreadyExecuted: 'Le raccourci a déjà été exécuté.',
  65. wrongDomain: 'Ce raccourci est censé d’être lancé uniquement sur YouTube.',
  66. success: 'YouTube est maintenant libre de pub ! Veuillez noter qu’il faudra rééxecuter le raccourci si vous rechargez la page.',
  67. },
  68. it: {
  69. logo: 'con&nbsp;AdGuard',
  70. alreadyExecuted: 'Il comando è già stato eseguito.',
  71. wrongDomain: 'Questa scorciatoia dovrebbe essere lanciata solo su YouTube.',
  72. success: 'YouTube è ora libero da pubblicità! Si prega di notare che è necessario eseguire nuovamente questa scorciatoia se ricarichi la pagina.',
  73. },
  74. 'zh-cn': {
  75. logo: '使用&nbsp;AdGuard',
  76. alreadyExecuted: '快捷指令已在运行',
  77. wrongDomain: '快捷指令只能在 YouTube 上被启动。',
  78. success: '现在您的 YouTube 没有广告!请注意,若您重新加载页面,您需要再次启动快捷指令。',
  79. },
  80. 'zh-tw': {
  81. logo: '偕同&nbsp;AdGuard',
  82. alreadyExecuted: '此捷徑已被執行。',
  83. wrongDomain: '此捷徑應該只於 YouTube 上被啟動。',
  84. success: '現在 YouTube 為無廣告的!請注意,若您重新載入該頁面,您需要再次執行此捷徑。',
  85. },
  86. ko: {
  87. logo: 'AdGuard&nbsp;사용',
  88. alreadyExecuted: '단축어가 이미 실행되었습니다.',
  89. wrongDomain: '이 단축어는 YouTube에서만 사용 가능합니다.',
  90. success: '이제 광고없이 YouTube를 시청할 수 있습니다. 페이지를 새로고침 할 경우, 이 단축어를 다시 실행해야 합니다.',
  91. },
  92. ja: {
  93. logo: 'AdGuard作動中',
  94. alreadyExecuted: 'ショートカットは既に実行されています。',
  95. wrongDomain: '※このショートカットは、YouTubeでのみ適用されることを想定しています。',
  96. success: 'YouTubeが広告なしになりました!※YouTubeページを再読み込みした場合は、このショートカットを再度実行する必要がありますのでご注意ください。',
  97. },
  98. uk: {
  99. logo: 'з&nbsp;AdGuard',
  100. alreadyExecuted: 'Ця швидка команда вже виконується.',
  101. wrongDomain: 'Цю швидку команду слід запускати лише на YouTube.',
  102. success: 'Тепер YouTube без реклами! Проте після перезавантаження сторінки необхідно знову запустити цю швидку команду.',
  103. },
  104. };
  105.  
  106. /**
  107. * Gets a localized message for the specified key
  108. *
  109. * @param {string} key message key
  110. * @returns {string} message for that key
  111. */
  112. const getMessage = (key) => {
  113. try {
  114. let locale = locales[navigator.language.toLowerCase()];
  115. if (!locale) {
  116. const lang = navigator.language.split('-')[0];
  117. locale = locales[lang];
  118. }
  119. if (!locale) {
  120. locale = locales.en;
  121. }
  122.  
  123. return locale[key];
  124. } catch (ex) {
  125. return locales.en[key];
  126. }
  127. };
  128.  
  129. if (document.getElementById('block-youtube-ads-logo')) {
  130. return {
  131. success: false,
  132. status: 'alreadyExecuted',
  133. message: getMessage('alreadyExecuted'),
  134. };
  135. }
  136.  
  137. if (window.location.hostname !== 'www.youtube.com'
  138. && window.location.hostname !== 'm.youtube.com'
  139. && window.location.hostname !== 'music.youtube.com') {
  140. return {
  141. success: false,
  142. status: 'wrongDomain',
  143. message: getMessage('wrongDomain'),
  144. };
  145. }
  146.  
  147. /**
  148. * Note that Shortcut scripts are executed in their own context (window)
  149. * and we don't have direct access to the real page window.
  150. *
  151. * In order to overcome this, we add a "script" to the page which is
  152. * executed in the proper context. The script content is inside
  153. * the "pageScript" function.
  154. */
  155. const pageScript = () => {
  156. const LOGO_ID = 'block-youtube-ads-logo';
  157.  
  158. const hiddenCSS = {
  159. 'www.youtube.com': [
  160. '#__ffYoutube1',
  161. '#__ffYoutube2',
  162. '#__ffYoutube3',
  163. '#__ffYoutube4',
  164. '#feed-pyv-container',
  165. '#feedmodule-PRO',
  166. '#homepage-chrome-side-promo',
  167. '#merch-shelf',
  168. '#offer-module',
  169. '#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]',
  170. '#pla-shelf',
  171. '#premium-yva',
  172. '#promo-info',
  173. '#promo-list',
  174. '#promotion-shelf',
  175. '#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer',
  176. '#search-pva',
  177. '#shelf-pyv-container',
  178. '#video-masthead',
  179. '#watch-branded-actions',
  180. '#watch-buy-urls',
  181. '#watch-channel-brand-div',
  182. '#watch7-branded-banner',
  183. '#YtKevlarVisibilityIdentifier',
  184. '#YtSparklesVisibilityIdentifier',
  185. '.carousel-offer-url-container',
  186. '.companion-ad-container',
  187. '.GoogleActiveViewElement',
  188. '.list-view[style="margin: 7px 0pt;"]',
  189. '.promoted-sparkles-text-search-root-container',
  190. '.promoted-videos',
  191. '.searchView.list-view',
  192. '.sparkles-light-cta',
  193. '.watch-extra-info-column',
  194. '.watch-extra-info-right',
  195. '.ytd-carousel-ad-renderer',
  196. '.ytd-compact-promoted-video-renderer',
  197. '.ytd-companion-slot-renderer',
  198. '.ytd-merch-shelf-renderer',
  199. '.ytd-player-legacy-desktop-watch-ads-renderer',
  200. '.ytd-promoted-sparkles-text-search-renderer',
  201. '.ytd-promoted-video-renderer',
  202. '.ytd-search-pyv-renderer',
  203. '.ytd-video-masthead-ad-v3-renderer',
  204. '.ytp-ad-action-interstitial-background-container',
  205. '.ytp-ad-action-interstitial-slot',
  206. '.ytp-ad-image-overlay',
  207. '.ytp-ad-overlay-container',
  208. '.ytp-ad-progress',
  209. '.ytp-ad-progress-list',
  210. '[class*="ytd-display-ad-"]',
  211. '[layout*="display-ad-"]',
  212. 'a[href^="http://www.youtube.com/cthru?"]',
  213. 'a[href^="https://www.youtube.com/cthru?"]',
  214. 'ytd-action-companion-ad-renderer',
  215. 'ytd-banner-promo-renderer',
  216. 'ytd-compact-promoted-video-renderer',
  217. 'ytd-companion-slot-renderer',
  218. 'ytd-display-ad-renderer',
  219. 'ytd-promoted-sparkles-text-search-renderer',
  220. 'ytd-promoted-sparkles-web-renderer',
  221. 'ytd-search-pyv-renderer',
  222. 'ytd-single-option-survey-renderer',
  223. 'ytd-video-masthead-ad-advertiser-info-renderer',
  224. 'ytd-video-masthead-ad-v3-renderer',
  225. 'YTM-PROMOTED-VIDEO-RENDERER',
  226. ],
  227. 'm.youtube.com': [
  228. '.companion-ad-container',
  229. '.ytp-ad-action-interstitial',
  230. '.ytp-cued-thumbnail-overlay > div[style*="/sddefault.jpg"]',
  231. 'a[href^="/watch?v="][onclick^="return koya.onEvent(arguments[0]||window.event,\'"]:not([role]):not([class]):not([id])',
  232. 'a[onclick*=\'"ping_url":"http://www.google.com/aclk?\']',
  233. 'ytm-companion-ad-renderer',
  234. 'ytm-companion-slot',
  235. 'ytm-promoted-sparkles-text-search-renderer',
  236. 'ytm-promoted-sparkles-web-renderer',
  237. 'ytm-promoted-video-renderer',
  238. ],
  239. };
  240.  
  241. /**
  242. * Adds CSS to the page
  243. * @param {string} hostname hostname
  244. */
  245. const hideElements = (hostname) => {
  246. const selectors = hiddenCSS[hostname];
  247. if (!selectors) {
  248. return;
  249. }
  250. const rule = `${selectors.join(', ')} { display: none!important; }`;
  251. const style = document.createElement('style');
  252. style.innerHTML = rule;
  253. document.head.appendChild(style);
  254. };
  255.  
  256. /**
  257. * Calls the "callback" function on every DOM change, but not for the tracked events
  258. * @param {Function} callback callback function
  259. */
  260. const observeDomChanges = (callback) => {
  261. const domMutationObserver = new MutationObserver((mutations) => {
  262. callback(mutations);
  263. });
  264.  
  265. domMutationObserver.observe(document.documentElement, {
  266. childList: true,
  267. subtree: true,
  268. });
  269. };
  270.  
  271. /**
  272. * This function is supposed to be called on every DOM change
  273. */
  274. const hideDynamicAds = () => {
  275. const elements = document.querySelectorAll('#contents > ytd-rich-item-renderer ytd-display-ad-renderer');
  276. if (elements.length === 0) {
  277. return;
  278. }
  279. elements.forEach((el) => {
  280. if (el.parentNode && el.parentNode.parentNode) {
  281. const parent = el.parentNode.parentNode;
  282. if (parent.localName === 'ytd-rich-item-renderer') {
  283. parent.style.display = 'none';
  284. }
  285. }
  286. });
  287. };
  288.  
  289. /**
  290. * This function checks if the video ads are currently running
  291. * and auto-clicks the skip button.
  292. */
  293. const autoSkipAds = () => {
  294. // If there's a video that plays the ad at this moment, scroll this ad
  295. if (document.querySelector('.ad-showing')) {
  296. const video = document.querySelector('video');
  297. if (video && video.duration) {
  298. video.currentTime = video.duration;
  299. // Skip button should appear after that,
  300. // now simply click it automatically
  301. setTimeout(() => {
  302. const skipBtn = document.querySelector('button.ytp-ad-skip-button');
  303. if (skipBtn) {
  304. skipBtn.click();
  305. }
  306. }, 100);
  307. }
  308. }
  309. };
  310.  
  311. /**
  312. * This function overrides a property on the specified object.
  313. *
  314. * @param {object} obj object to look for properties in
  315. * @param {string} propertyName property to override
  316. * @param {*} overrideValue value to set
  317. */
  318. const overrideObject = (obj, propertyName, overrideValue) => {
  319. if (!obj) {
  320. return false;
  321. }
  322. let overriden = false;
  323.  
  324. for (const key in obj) {
  325. if (obj.hasOwnProperty(key) && key === propertyName) {
  326. obj[key] = overrideValue;
  327. overriden = true;
  328. } else if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
  329. if (overrideObject(obj[key], propertyName, overrideValue)) {
  330. overriden = true;
  331. }
  332. }
  333. }
  334.  
  335. if (overriden) {
  336. console.log(`found: ${propertyName}`);
  337. }
  338.  
  339. return overriden;
  340. };
  341.  
  342. /**
  343. * Overrides JSON.parse and Response.json functions.
  344. * Examines these functions arguments, looks for properties with the specified name there
  345. * and if it exists, changes it's value to what was specified.
  346. *
  347. * @param {string} propertyName name of the property
  348. * @param {*} overrideValue new value for the property
  349. */
  350. const jsonOverride = (propertyName, overrideValue) => {
  351. const nativeJSONParse = JSON.parse;
  352. JSON.parse = (...args) => {
  353. const obj = nativeJSONParse.apply(this, args);
  354.  
  355. // Override it's props and return back to the caller
  356. overrideObject(obj, propertyName, overrideValue);
  357. return obj;
  358. };
  359.  
  360. // Override Response.prototype.json
  361. const nativeResponseJson = Response.prototype.json;
  362. Response.prototype.json = new Proxy(nativeResponseJson, {
  363. apply(...args) {
  364. // Call the target function, get the original Promise
  365. const promise = Reflect.apply(args);
  366.  
  367. // Create a new one and override the JSON inside
  368. return new Promise((resolve, reject) => {
  369. promise.then((data) => {
  370. overrideObject(data, propertyName, overrideValue);
  371. resolve(data);
  372. }).catch((error) => reject(error));
  373. });
  374. },
  375. });
  376. };
  377.  
  378. const addAdGuardLogoStyle = () => {
  379. const id = 'block-youtube-ads-logo-style';
  380. if (document.getElementById(id)) {
  381. return;
  382. }
  383.  
  384. // Here is what these styles do:
  385. // 1. Change AG marker color depending on the page
  386. // 2. Hide Sign-in button on m.youtube.com otherwise it does not look good
  387. // It is still possible to sign in by clicking "three dots" button.
  388. // 3. Hide the marker when the user is searching for something
  389. // 4. On YT Music apply display:block to the logo element
  390. const style = document.createElement('style');
  391. style.innerHTML = `[data-mode="watch"] #${LOGO_ID} { color: #fff; }
  392. [data-mode="searching"] #${LOGO_ID}, [data-mode="search"] #${LOGO_ID} { display: none; }
  393. #${LOGO_ID} { white-space: nowrap; }
  394. .mobile-topbar-header-sign-in-button { display: none; }
  395. .ytmusic-nav-bar#left-content #${LOGO_ID} { display: block; }`;
  396. document.head.appendChild(style);
  397. };
  398.  
  399. const addAdGuardLogo = () => {
  400. if (document.getElementById(LOGO_ID)) {
  401. return;
  402. }
  403.  
  404. const logo = document.createElement('span');
  405. logo.innerHTML = '__logo_text__';
  406. logo.setAttribute('id', LOGO_ID);
  407.  
  408. if (window.location.hostname === 'm.youtube.com') {
  409. const btn = document.querySelector('header.mobile-topbar-header > button');
  410. if (btn) {
  411. btn.parentNode.insertBefore(logo, btn.nextSibling);
  412. addAdGuardLogoStyle();
  413. }
  414. } else if (window.location.hostname === 'www.youtube.com') {
  415. const code = document.getElementById('country-code');
  416. if (code) {
  417. code.innerHTML = '';
  418. code.appendChild(logo);
  419. addAdGuardLogoStyle();
  420. }
  421. } else if (window.location.hostname === 'music.youtube.com') {
  422. const el = document.querySelector('.ytmusic-nav-bar#left-content');
  423. if (el) {
  424. el.appendChild(logo);
  425. addAdGuardLogoStyle();
  426. }
  427. }
  428. };
  429.  
  430. // Removes ads metadata from YouTube XHR requests
  431. jsonOverride('adPlacements', []);
  432. jsonOverride('playerAds', []);
  433.  
  434. // Applies CSS that hides YouTube ad elements
  435. hideElements(window.location.hostname);
  436.  
  437. // Some changes should be re-evaluated on every page change
  438. addAdGuardLogo();
  439. hideDynamicAds();
  440. autoSkipAds();
  441. observeDomChanges(() => {
  442. addAdGuardLogo();
  443. hideDynamicAds();
  444. autoSkipAds();
  445. });
  446. };
  447.  
  448. const script = document.createElement('script');
  449. const scriptText = pageScript.toString().replace('__logo_text__', getMessage('logo'));
  450. script.innerHTML = `(${scriptText})();`;
  451. document.head.appendChild(script);
  452. document.head.removeChild(script);
  453.  
  454. return {
  455. success: true,
  456. status: 'success',
  457. message: getMessage('success'),
  458. };
  459. }
  460.  
  461. /**
  462. * Runs the shortcut
  463. */
  464. (() => {
  465. // "completion" function is only defined if this script is launched as Shortcut
  466. // in other cases we simply polyfill it.
  467. let finish = (m) => { console.log(m); };
  468. if (typeof completion !== 'undefined') {
  469. finish = completion;
  470. }
  471.  
  472. try {
  473. const result = runBlockYoutube();
  474. finish(result.message);
  475. } catch (ex) {
  476. finish(ex.toString());
  477. }
  478. })();

QingJ © 2025

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