new plugin WatchTogetherAdblock: block ads in youtube activity (#2021)
Co-authored-by: vee <vendicated@riseup.net>
This commit is contained in:
parent
9ec671819d
commit
41c5bbd952
5 changed files with 305 additions and 3 deletions
|
@ -204,9 +204,7 @@ export const fileUrlPlugin = {
|
||||||
const res = await esbuild.build({
|
const res = await esbuild.build({
|
||||||
entryPoints: [path],
|
entryPoints: [path],
|
||||||
write: false,
|
write: false,
|
||||||
minify: true,
|
minify: true
|
||||||
bundle: true,
|
|
||||||
format: "esm"
|
|
||||||
});
|
});
|
||||||
content = res.outputFiles[0].text;
|
content = res.outputFiles[0].text;
|
||||||
} else {
|
} else {
|
||||||
|
|
6
src/plugins/watchTogetherAdblock.desktop/README.md
Normal file
6
src/plugins/watchTogetherAdblock.desktop/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# WatchTogetherAdblock
|
||||||
|
|
||||||
|
Block ads in the YouTube WatchTogether activity via AdGuard
|
||||||
|
|
||||||
|
Note that this only works for yourself, other users in the activity will still see ads.
|
||||||
|
Powered by a modified version of [Adguard's BlockYoutubeAdsShortcut](https://github.com/AdguardTeam/BlockYouTubeAdsShortcut)
|
262
src/plugins/watchTogetherAdblock.desktop/adguard.js
Normal file
262
src/plugins/watchTogetherAdblock.desktop/adguard.js
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of AdGuard's Block YouTube Ads (https://github.com/AdguardTeam/BlockYouTubeAdsShortcut).
|
||||||
|
*
|
||||||
|
* Copyright (C) AdGuard Team
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LOGO_ID = "block-youtube-ads-logo";
|
||||||
|
const hiddenCSS = [
|
||||||
|
"#__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",
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* Adds CSS to the page
|
||||||
|
*/
|
||||||
|
const hideElements = () => {
|
||||||
|
const selectors = hiddenCSS;
|
||||||
|
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) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (obj.hasOwnProperty(key) && key === propertyName) {
|
||||||
|
obj[key] = overrideValue;
|
||||||
|
overriden = true;
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
} else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
|
||||||
|
if (overrideObject(obj[key], propertyName, overrideValue)) {
|
||||||
|
overriden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
} else if (window.location.hostname === "www.youtube-nocookie.com") {
|
||||||
|
const code = document.querySelector("#yt-masthead #logo-container .content-region");
|
||||||
|
if (code) {
|
||||||
|
code.innerHTML = "";
|
||||||
|
code.appendChild(logo);
|
||||||
|
addAdGuardLogoStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Removes ads metadata from YouTube XHR requests
|
||||||
|
jsonOverride("adPlacements", []);
|
||||||
|
jsonOverride("playerAds", []);
|
||||||
|
// Applies CSS that hides YouTube ad elements
|
||||||
|
hideElements();
|
||||||
|
// Some changes should be re-evaluated on every page change
|
||||||
|
addAdGuardLogo();
|
||||||
|
hideDynamicAds();
|
||||||
|
autoSkipAds();
|
||||||
|
observeDomChanges(() => {
|
||||||
|
addAdGuardLogo();
|
||||||
|
hideDynamicAds();
|
||||||
|
autoSkipAds();
|
||||||
|
});
|
15
src/plugins/watchTogetherAdblock.desktop/index.ts
Normal file
15
src/plugins/watchTogetherAdblock.desktop/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
// The entire code of this plugin can be found in native.ts
|
||||||
|
export default definePlugin({
|
||||||
|
name: "WatchTogetherAdblock",
|
||||||
|
description: "Block ads in the YouTube WatchTogether activity via AdGuard",
|
||||||
|
authors: [Devs.ImLvna],
|
||||||
|
});
|
21
src/plugins/watchTogetherAdblock.desktop/native.ts
Normal file
21
src/plugins/watchTogetherAdblock.desktop/native.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RendererSettings } from "@main/settings";
|
||||||
|
import { app } from "electron";
|
||||||
|
import adguard from "file://adguard.js?minify";
|
||||||
|
|
||||||
|
app.on("browser-window-created", (_, win) => {
|
||||||
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
|
frame.once("dom-ready", () => {
|
||||||
|
if (frame.url.includes("discordsays") && frame.url.includes("youtube.com")) {
|
||||||
|
if (!RendererSettings.store.plugins?.WatchTogetherAdblock?.enabled) return;
|
||||||
|
|
||||||
|
frame.executeJavaScript(adguard);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue