mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-05-11 01:45:48 +02:00
fix YoutubeAdblock
This commit is contained in:
parent
dcb7a593e5
commit
14cdbaa1dd
5 changed files with 150 additions and 227 deletions
|
@ -280,7 +280,8 @@ export const fileUrlPlugin = {
|
|||
const res = await esbuild.build({
|
||||
entryPoints: [path],
|
||||
write: false,
|
||||
minify: true
|
||||
minify: true,
|
||||
bundle: true
|
||||
});
|
||||
content = res.outputFiles[0].text;
|
||||
} else {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { app } from "electron";
|
|||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame?.once("dom-ready", () => {
|
||||
if (frame.url.startsWith("https://www.youtube.com/")) {
|
||||
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
|
||||
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
||||
if (!settings?.enabled) return;
|
||||
|
||||
|
|
137
src/plugins/youtubeAdblock.desktop/adblock-runtime.ts
Normal file
137
src/plugins/youtubeAdblock.desktop/adblock-runtime.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// very loosely based on (but fully rewritten)
|
||||
// https://github.com/ParticleCore/Iridium/blob/be0acb55146aac60c34eef3fe22f3dda407aa2fa/src/chrome/js/background-inject.js
|
||||
|
||||
type AnyObject = Record<PropertyKey, any>;
|
||||
|
||||
interface Window {
|
||||
fetch: typeof fetch & { original?: typeof fetch; };
|
||||
}
|
||||
|
||||
function findObjectWithPropertyRecursive(obj: AnyObject, key: PropertyKey) {
|
||||
if (!obj || typeof obj !== "object") return;
|
||||
|
||||
if (Object.hasOwn(obj, key)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
for (const child of Object.values(obj)) {
|
||||
if (!child) continue;
|
||||
|
||||
if (Array.isArray(child)) {
|
||||
for (const element of child) {
|
||||
const result = findObjectWithPropertyRecursive(element, key);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (typeof child === "object") {
|
||||
const result = findObjectWithPropertyRecursive(child, key);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAds(obj: AnyObject) {
|
||||
const adPlacementsParent = findObjectWithPropertyRecursive(obj, "adPlacements");
|
||||
const adSlotsParent = findObjectWithPropertyRecursive(obj, "adSlots");
|
||||
const playerAdsParent = findObjectWithPropertyRecursive(obj, "playerAds");
|
||||
|
||||
if (adPlacementsParent?.adPlacements) {
|
||||
delete adPlacementsParent.adPlacements;
|
||||
}
|
||||
if (adSlotsParent?.adSlots) {
|
||||
delete adSlotsParent.adSlots;
|
||||
}
|
||||
if (playerAdsParent?.playerAds) {
|
||||
delete playerAdsParent.playerAds;
|
||||
}
|
||||
}
|
||||
|
||||
const handleResponseKey = Symbol("handleResponse");
|
||||
|
||||
Object.defineProperty(Object.prototype, "handleResponse", {
|
||||
set(data) {
|
||||
this[handleResponseKey] = data;
|
||||
},
|
||||
get() {
|
||||
const original = this[handleResponseKey];
|
||||
if (!original) return undefined;
|
||||
|
||||
return function (this: any, _url, _code, response, _callback) {
|
||||
if (typeof response === "string" && original?.toString().includes('")]}\'"')) {
|
||||
try {
|
||||
const parsed = JSON.parse(response);
|
||||
deleteAds(parsed);
|
||||
arguments[2] = JSON.stringify(parsed);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
return original.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function shouldDeleteAds(data: AnyObject) {
|
||||
if (!data) return false;
|
||||
|
||||
const endpoints = data?.onResponseReceivedEndpoints;
|
||||
|
||||
if (Array.isArray(endpoints)) {
|
||||
if (endpoints.some(e => e?.reloadContinuationItemsCommand?.targetId === "comments-section"))
|
||||
return false;
|
||||
}
|
||||
|
||||
return ["contents", "videoDetails", "items", "onResponseReceivedActions", "onResponseReceivedEndpoints", "onResponseReceivedCommands"].some(key => data[key]);
|
||||
}
|
||||
|
||||
const originalFetch = window.fetch?.original || window.fetch;
|
||||
window.fetch = async function fetch(input, init) {
|
||||
const res = await originalFetch(input, init);
|
||||
try {
|
||||
const text = await res.clone().text();
|
||||
const data = JSON.parse(text.replace(")]}'\n", ""));
|
||||
|
||||
if (!shouldDeleteAds(data)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
deleteAds(data);
|
||||
return new Response(JSON.stringify(data));
|
||||
} catch (e) {
|
||||
return res;
|
||||
}
|
||||
};
|
||||
window.fetch.original = originalFetch;
|
||||
|
||||
let ytInitialData: any;
|
||||
let ytInitialPlayerResponse: any;
|
||||
|
||||
Object.defineProperty(window, "ytInitialData", {
|
||||
get() {
|
||||
return ytInitialData;
|
||||
},
|
||||
set(data) {
|
||||
deleteAds(data);
|
||||
ytInitialData = data;
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "ytInitialPlayerResponse", {
|
||||
get() {
|
||||
return ytInitialPlayerResponse;
|
||||
},
|
||||
set(data) {
|
||||
deleteAds(data);
|
||||
ytInitialPlayerResponse = data;
|
||||
}
|
||||
});
|
|
@ -1,216 +0,0 @@
|
|||
/* 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 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.textContent = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
Response.prototype.json = new Proxy(Response.prototype.json, {
|
||||
async apply(...args) {
|
||||
// Call the target function, get the original Promise
|
||||
const result = await Reflect.apply(...args);
|
||||
// Create a new one and override the JSON inside
|
||||
overrideObject(result, propertyName, overrideValue);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
};
|
||||
// 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
|
||||
hideDynamicAds();
|
||||
autoSkipAds();
|
||||
observeDomChanges(() => {
|
||||
hideDynamicAds();
|
||||
autoSkipAds();
|
||||
});
|
|
@ -5,17 +5,18 @@
|
|||
*/
|
||||
|
||||
import { RendererSettings } from "@main/settings";
|
||||
import { app } from "electron";
|
||||
import adguard from "file://adguard.js?minify";
|
||||
import { app, webFrameMain } from "electron";
|
||||
import adguard from "file://adblock-runtime.ts?minify";
|
||||
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame?.once("dom-ready", () => {
|
||||
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
|
||||
win.webContents.on("did-frame-navigate", (_event, _url, _httpResponseCose, _httpStatusText, _isMainFrame, frameProcessid, frameRoutingId) => {
|
||||
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
|
||||
|
||||
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
|
||||
frame.executeJavaScript(adguard);
|
||||
}
|
||||
});
|
||||
const frame = webFrameMain.fromId(frameProcessid, frameRoutingId);
|
||||
if (!frame) return;
|
||||
|
||||
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
|
||||
frame.executeJavaScript(adguard);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue