mirror of
https://github.com/System-End/Vencord.git
synced 2026-04-19 16:28:16 +00: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({
|
const res = await esbuild.build({
|
||||||
entryPoints: [path],
|
entryPoints: [path],
|
||||||
write: false,
|
write: false,
|
||||||
minify: true
|
minify: true,
|
||||||
|
bundle: true
|
||||||
});
|
});
|
||||||
content = res.outputFiles[0].text;
|
content = res.outputFiles[0].text;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { app } from "electron";
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
frame?.once("dom-ready", () => {
|
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;
|
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
||||||
if (!settings?.enabled) return;
|
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 { RendererSettings } from "@main/settings";
|
||||||
import { app } from "electron";
|
import { app, webFrameMain } from "electron";
|
||||||
import adguard from "file://adguard.js?minify";
|
import adguard from "file://adblock-runtime.ts?minify";
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("did-frame-navigate", (_event, _url, _httpResponseCose, _httpStatusText, _isMainFrame, frameProcessid, frameRoutingId) => {
|
||||||
frame?.once("dom-ready", () => {
|
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
|
||||||
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
|
|
||||||
|
|
||||||
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
|
const frame = webFrameMain.fromId(frameProcessid, frameRoutingId);
|
||||||
frame.executeJavaScript(adguard);
|
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
Reference in a new issue