您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block Ads YouTube use AdGuard script
// ==UserScript== // @name AdGuard script block YouTube ads // @version 0.0.1 // @match *://*.youtube.com/* // @grant none // @noframes // @run-at document-idle // @namespace https://greasyfork.org/users/848349 // @description Block Ads YouTube use AdGuard script // ==/UserScript== /** * This file is part of AdGuard's Block YouTube Ads (https://github.com/AdguardTeam/BlockYouTubeAdsShortcut). * * AdGuard's Block YouTube Ads is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * AdGuard's Block YouTube Ads is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with AdGuard's Block YouTube Ads. If not, see <http://www.gnu.org/licenses/>. */ /* global Response, window, navigator, document, MutationObserver, completion */ /** * The function that implements all the logic. * Returns the run status. */ function runBlockYoutube() { const locales = { en: { logo: 'with AdGuard', alreadyExecuted: 'The shortcut has already been executed.', wrongDomain: 'This shortcut is supposed to be launched only on YouTube.', success: 'YouTube is now ad-free! Please note that you need to run this shortcut again if you reload the page.', }, ru: { logo: 'с AdGuard', alreadyExecuted: 'Быстрая команда уже выполнена.', wrongDomain: 'Эта быстрая команда предназначена для использования только на YouTube.', success: 'Теперь YouTube без рекламы! Важно: при перезагрузке страницы вам нужно будет заново запустить команду.', }, es: { logo: 'con AdGuard', alreadyExecuted: 'El atajo ya ha sido ejecutado.', wrongDomain: 'Se supone que este atajo se lanza sólo en YouTube.', success: '¡YouTube está ahora libre de anuncios! Ten en cuenta que tienes que volver a ejecutar este atajo si recargas la página.', }, de: { logo: 'mit AdGuard', alreadyExecuted: 'Der Kurzbefehl wurde bereits ausgeführt.', wrongDomain: 'Dieser Kurzbefehl soll nur auf YouTube gestartet werden.', success: 'YouTube ist jetzt werbefrei! Bitte beachten Sie, dass Sie diesen Kurzbefehl erneut ausführen müssen, wenn Sie die Seite neu laden.', }, fr: { logo: 'avec AdGuard', alreadyExecuted: 'Le raccourci a déjà été exécuté.', wrongDomain: 'Ce raccourci est censé d’être lancé uniquement sur YouTube.', success: 'YouTube est maintenant libre de pub ! Veuillez noter qu’il faudra rééxecuter le raccourci si vous rechargez la page.', }, it: { logo: 'con AdGuard', alreadyExecuted: 'Il comando è già stato eseguito.', wrongDomain: 'Questa scorciatoia dovrebbe essere lanciata solo su YouTube.', success: 'YouTube è ora libero da pubblicità! Si prega di notare che è necessario eseguire nuovamente questa scorciatoia se ricarichi la pagina.', }, 'zh-cn': { logo: '使用 AdGuard', alreadyExecuted: '快捷指令已在运行', wrongDomain: '快捷指令只能在 YouTube 上被启动。', success: '现在您的 YouTube 没有广告!请注意,若您重新加载页面,您需要再次启动快捷指令。', }, 'zh-tw': { logo: '偕同 AdGuard', alreadyExecuted: '此捷徑已被執行。', wrongDomain: '此捷徑應該只於 YouTube 上被啟動。', success: '現在 YouTube 為無廣告的!請注意,若您重新載入該頁面,您需要再次執行此捷徑。', }, ko: { logo: 'AdGuard 사용', alreadyExecuted: '단축어가 이미 실행되었습니다.', wrongDomain: '이 단축어는 YouTube에서만 사용 가능합니다.', success: '이제 광고없이 YouTube를 시청할 수 있습니다. 페이지를 새로고침 할 경우, 이 단축어를 다시 실행해야 합니다.', }, ja: { logo: 'AdGuard作動中', alreadyExecuted: 'ショートカットは既に実行されています。', wrongDomain: '※このショートカットは、YouTubeでのみ適用されることを想定しています。', success: 'YouTubeが広告なしになりました!※YouTubeページを再読み込みした場合は、このショートカットを再度実行する必要がありますのでご注意ください。', }, uk: { logo: 'з AdGuard', alreadyExecuted: 'Ця швидка команда вже виконується.', wrongDomain: 'Цю швидку команду слід запускати лише на YouTube.', success: 'Тепер YouTube без реклами! Проте після перезавантаження сторінки необхідно знову запустити цю швидку команду.', }, }; /** * Gets a localized message for the specified key * * @param {string} key message key * @returns {string} message for that key */ const getMessage = (key) => { try { let locale = locales[navigator.language.toLowerCase()]; if (!locale) { const lang = navigator.language.split('-')[0]; locale = locales[lang]; } if (!locale) { locale = locales.en; } return locale[key]; } catch (ex) { return locales.en[key]; } }; if (document.getElementById('block-youtube-ads-logo')) { return { success: false, status: 'alreadyExecuted', message: getMessage('alreadyExecuted'), }; } if (window.location.hostname !== 'www.youtube.com' && window.location.hostname !== 'm.youtube.com' && window.location.hostname !== 'music.youtube.com') { return { success: false, status: 'wrongDomain', message: getMessage('wrongDomain'), }; } /** * Note that Shortcut scripts are executed in their own context (window) * and we don't have direct access to the real page window. * * In order to overcome this, we add a "script" to the page which is * executed in the proper context. The script content is inside * the "pageScript" function. */ const pageScript = () => { const LOGO_ID = 'block-youtube-ads-logo'; const hiddenCSS = { 'www.youtube.com': [ '#__ffYoutube1', '#__ffYoutube2', '#__ffYoutube3', '#__ffYoutube4', '#feed-pyv-container', '#feedmodule-PRO', '#homepage-chrome-side-promo', '#merch-shelf', '#offer-module', '#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]', '#pla-shelf', '#premium-yva', '#promo-info', '#promo-list', '#promotion-shelf', '#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer', '#search-pva', '#shelf-pyv-container', '#video-masthead', '#watch-branded-actions', '#watch-buy-urls', '#watch-channel-brand-div', '#watch7-branded-banner', '#YtKevlarVisibilityIdentifier', '#YtSparklesVisibilityIdentifier', '.carousel-offer-url-container', '.companion-ad-container', '.GoogleActiveViewElement', '.list-view[style="margin: 7px 0pt;"]', '.promoted-sparkles-text-search-root-container', '.promoted-videos', '.searchView.list-view', '.sparkles-light-cta', '.watch-extra-info-column', '.watch-extra-info-right', '.ytd-carousel-ad-renderer', '.ytd-compact-promoted-video-renderer', '.ytd-companion-slot-renderer', '.ytd-merch-shelf-renderer', '.ytd-player-legacy-desktop-watch-ads-renderer', '.ytd-promoted-sparkles-text-search-renderer', '.ytd-promoted-video-renderer', '.ytd-search-pyv-renderer', '.ytd-video-masthead-ad-v3-renderer', '.ytp-ad-action-interstitial-background-container', '.ytp-ad-action-interstitial-slot', '.ytp-ad-image-overlay', '.ytp-ad-overlay-container', '.ytp-ad-progress', '.ytp-ad-progress-list', '[class*="ytd-display-ad-"]', '[layout*="display-ad-"]', 'a[href^="http://www.youtube.com/cthru?"]', 'a[href^="https://www.youtube.com/cthru?"]', 'ytd-action-companion-ad-renderer', 'ytd-banner-promo-renderer', 'ytd-compact-promoted-video-renderer', 'ytd-companion-slot-renderer', 'ytd-display-ad-renderer', 'ytd-promoted-sparkles-text-search-renderer', 'ytd-promoted-sparkles-web-renderer', 'ytd-search-pyv-renderer', 'ytd-single-option-survey-renderer', 'ytd-video-masthead-ad-advertiser-info-renderer', 'ytd-video-masthead-ad-v3-renderer', 'YTM-PROMOTED-VIDEO-RENDERER', ], 'm.youtube.com': [ '.companion-ad-container', '.ytp-ad-action-interstitial', '.ytp-cued-thumbnail-overlay > div[style*="/sddefault.jpg"]', 'a[href^="/watch?v="][onclick^="return koya.onEvent(arguments[0]||window.event,\'"]:not([role]):not([class]):not([id])', 'a[onclick*=\'"ping_url":"http://www.google.com/aclk?\']', 'ytm-companion-ad-renderer', 'ytm-companion-slot', 'ytm-promoted-sparkles-text-search-renderer', 'ytm-promoted-sparkles-web-renderer', 'ytm-promoted-video-renderer', ], }; /** * Adds CSS to the page * @param {string} hostname hostname */ const hideElements = (hostname) => { const selectors = hiddenCSS[hostname]; if (!selectors) { return; } const rule = `${selectors.join(', ')} { display: none!important; }`; const style = document.createElement('style'); style.innerHTML = rule; document.head.appendChild(style); }; /** * Calls the "callback" function on every DOM change, but not for the tracked events * @param {Function} callback callback function */ const observeDomChanges = (callback) => { const domMutationObserver = new MutationObserver((mutations) => { callback(mutations); }); domMutationObserver.observe(document.documentElement, { childList: true, subtree: true, }); }; /** * This function is supposed to be called on every DOM change */ const hideDynamicAds = () => { const elements = document.querySelectorAll('#contents > ytd-rich-item-renderer ytd-display-ad-renderer'); if (elements.length === 0) { return; } elements.forEach((el) => { if (el.parentNode && el.parentNode.parentNode) { const parent = el.parentNode.parentNode; if (parent.localName === 'ytd-rich-item-renderer') { parent.style.display = 'none'; } } }); }; /** * This function checks if the video ads are currently running * and auto-clicks the skip button. */ const autoSkipAds = () => { // If there's a video that plays the ad at this moment, scroll this ad if (document.querySelector('.ad-showing')) { const video = document.querySelector('video'); if (video && video.duration) { video.currentTime = video.duration; // Skip button should appear after that, // now simply click it automatically setTimeout(() => { const skipBtn = document.querySelector('button.ytp-ad-skip-button'); if (skipBtn) { skipBtn.click(); } }, 100); } } }; /** * This function overrides a property on the specified object. * * @param {object} obj object to look for properties in * @param {string} propertyName property to override * @param {*} overrideValue value to set */ const overrideObject = (obj, propertyName, overrideValue) => { if (!obj) { return false; } let overriden = false; for (const key in obj) { if (obj.hasOwnProperty(key) && key === propertyName) { obj[key] = overrideValue; overriden = true; } else if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') { if (overrideObject(obj[key], propertyName, overrideValue)) { overriden = true; } } } if (overriden) { console.log(`found: ${propertyName}`); } return overriden; }; /** * Overrides JSON.parse and Response.json functions. * Examines these functions arguments, looks for properties with the specified name there * and if it exists, changes it's value to what was specified. * * @param {string} propertyName name of the property * @param {*} overrideValue new value for the property */ const jsonOverride = (propertyName, overrideValue) => { const nativeJSONParse = JSON.parse; JSON.parse = (...args) => { const obj = nativeJSONParse.apply(this, args); // Override it's props and return back to the caller overrideObject(obj, propertyName, overrideValue); return obj; }; // Override Response.prototype.json const nativeResponseJson = Response.prototype.json; Response.prototype.json = new Proxy(nativeResponseJson, { apply(...args) { // Call the target function, get the original Promise const promise = Reflect.apply(args); // Create a new one and override the JSON inside return new Promise((resolve, reject) => { promise.then((data) => { overrideObject(data, propertyName, overrideValue); resolve(data); }).catch((error) => reject(error)); }); }, }); }; const addAdGuardLogoStyle = () => { const id = 'block-youtube-ads-logo-style'; if (document.getElementById(id)) { return; } // Here is what these styles do: // 1. Change AG marker color depending on the page // 2. Hide Sign-in button on m.youtube.com otherwise it does not look good // It is still possible to sign in by clicking "three dots" button. // 3. Hide the marker when the user is searching for something // 4. On YT Music apply display:block to the logo element const style = document.createElement('style'); style.innerHTML = `[data-mode="watch"] #${LOGO_ID} { color: #fff; } [data-mode="searching"] #${LOGO_ID}, [data-mode="search"] #${LOGO_ID} { display: none; } #${LOGO_ID} { white-space: nowrap; } .mobile-topbar-header-sign-in-button { display: none; } .ytmusic-nav-bar#left-content #${LOGO_ID} { display: block; }`; document.head.appendChild(style); }; const addAdGuardLogo = () => { if (document.getElementById(LOGO_ID)) { return; } const logo = document.createElement('span'); logo.innerHTML = '__logo_text__'; logo.setAttribute('id', LOGO_ID); if (window.location.hostname === 'm.youtube.com') { const btn = document.querySelector('header.mobile-topbar-header > button'); if (btn) { btn.parentNode.insertBefore(logo, btn.nextSibling); addAdGuardLogoStyle(); } } else if (window.location.hostname === 'www.youtube.com') { const code = document.getElementById('country-code'); if (code) { code.innerHTML = ''; code.appendChild(logo); addAdGuardLogoStyle(); } } else if (window.location.hostname === 'music.youtube.com') { const el = document.querySelector('.ytmusic-nav-bar#left-content'); if (el) { el.appendChild(logo); addAdGuardLogoStyle(); } } }; // Removes ads metadata from YouTube XHR requests jsonOverride('adPlacements', []); jsonOverride('playerAds', []); // Applies CSS that hides YouTube ad elements hideElements(window.location.hostname); // Some changes should be re-evaluated on every page change addAdGuardLogo(); hideDynamicAds(); autoSkipAds(); observeDomChanges(() => { addAdGuardLogo(); hideDynamicAds(); autoSkipAds(); }); }; const script = document.createElement('script'); const scriptText = pageScript.toString().replace('__logo_text__', getMessage('logo')); script.innerHTML = `(${scriptText})();`; document.head.appendChild(script); document.head.removeChild(script); return { success: true, status: 'success', message: getMessage('success'), }; } /** * Runs the shortcut */ (() => { // "completion" function is only defined if this script is launched as Shortcut // in other cases we simply polyfill it. let finish = (m) => { console.log(m); }; if (typeof completion !== 'undefined') { finish = completion; } try { const result = runBlockYoutube(); finish(result.message); } catch (ex) { finish(ex.toString()); } })();