Skip to content

Commit

Permalink
Better YouTube Embeds
Browse files Browse the repository at this point in the history
  • Loading branch information
Cynosphere committed Feb 4, 2025
1 parent d6f6e40 commit 6da54e2
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 1 deletion.
4 changes: 3 additions & 1 deletion build.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { watchExt, buildExt } from "@moonlight-mod/esbuild-config";
import inlineImportPlugin from "esbuild-plugin-inline-import";

const watch = process.argv.includes("--watch");
const clean = process.argv.includes("--clean");
Expand All @@ -14,7 +15,8 @@ if (clean) {
const cfg = {
src: path.resolve(path.join("src", ext)),
dst: path.resolve(path.join("dist", ext)),
ext
ext,
extraPlugins: [inlineImportPlugin()]
};

if (watch) {
Expand Down
6 changes: 6 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
/// <reference types="@moonlight-mod/types" />

// esbuild-plugin-inline-import
declare module "inline:*" {
const content: string;
export default content;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@moonlight-mod/eslint-config": "github:moonlight-mod/eslint-config",
"esbuild": "^0.19.3",
"esbuild-copy-static-files": "^0.1.0",
"esbuild-plugin-inline-import": "^1.1.0",
"eslint": "^9.17.0",
"typescript": "^5.3.2"
},
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

168 changes: 168 additions & 0 deletions src/betterEmbedsYT/adblock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// https://github.com/ParticleCore/Iridium/blob/be0acb55146aac60c34eef3fe22f3dda407aa2fa/src/chrome/js/background-inject.js

const Util = {
getSingleObjectAndParentByKey: (obj, keys, match) => {
for (let property in obj) {
if (!Object.hasOwn(obj, property) || obj[property] === null || obj[property] === undefined) {
continue;
}

if (
(keys.constructor.name === "String" ? keys === property : keys.indexOf(property) > -1) &&
(!match || match(obj[property], obj))
) {
return {
parent: obj,
object: obj[property]
};
}

if (obj[property].constructor.name === "Object") {
let result = Util.getSingleObjectAndParentByKey(obj[property], keys, match);
if (result) {
return result;
}
} else if (obj[property].constructor.name === "Array") {
for (let i = 0; i < obj[property].length; i++) {
let result = Util.getSingleObjectAndParentByKey(obj[property][i], keys, match);
if (result) {
return result;
}
}
}
}
}
};

const OverrideHandleResponse = (() => {
const listeners = [];
const handleResponseKey = crypto.randomUUID();

Object.defineProperty(Object.prototype, "handleResponse", {
set(data) {
this[handleResponseKey] = data;
},
get() {
const original = this[handleResponseKey];
return function (url, code, response, callback) {
if (response?.constructor === String && original?.toString()?.indexOf('")]}\'"') !== -1) {
try {
const parsed = JSON.parse(response);
listeners?.forEach((listener) => listener?.(parsed));
arguments[2] = JSON.stringify(parsed);
} catch {
//
}
}
return original?.apply(this, arguments);
};
}
});

return {
onHandleResponseListener: (listener) => listeners.push(listener)
};
})();

const OverrideFetch = (() => {
const listeners = [];

const canProceed = (data) => {
const endpoints = data?.["onResponseReceivedEndpoints"];

if (endpoints != null && endpoints?.constructor === Array && endpoints.length > 0) {
for (let i = endpoints.length - 1; i >= 0; i--) {
if (endpoints[i]?.["reloadContinuationItemsCommand"]?.["targetId"] === "comments-section") {
return false;
}
}
}

return (
data?.["contents"] ||
data?.["videoDetails"] ||
data?.["items"] ||
data?.["onResponseReceivedActions"] ||
data?.["onResponseReceivedEndpoints"] ||
data?.["onResponseReceivedCommands"]
);
};

const override = function (target, thisArg, argArray) {
if (!argArray?.[0]?.url || Object.getOwnPropertyDescriptor(argArray[0], "url") !== undefined) {
return target.apply(thisArg, argArray);
} else {
return target.apply(thisArg, argArray).then((response) => {
return response
.clone()
.text()
.then((text) => {
const data = JSON.parse(text.replace(")]}'\n", ""));
if (canProceed(data)) {
listeners?.forEach((listener) => listener?.(data));
return new Response(JSON.stringify(data));
} else {
return response;
}
})
.catch(() => response);
});
}
};

const original = window.fetch?.["original"] || window.fetch;

window.fetch = new Proxy(window.fetch, { apply: override });
window.fetch.original = original;

let ytInitialData = undefined;

Object.defineProperty(window, "ytInitialData", {
set(data) {
ytInitialData = data;
listeners?.forEach((listener) => listener?.(ytInitialData));
},
get() {
return ytInitialData;
}
});

let ytInitialPlayerResponse = undefined;

Object.defineProperty(window, "ytInitialPlayerResponse", {
set(data) {
ytInitialPlayerResponse = data;
listeners?.forEach((listener) => listener?.(ytInitialPlayerResponse));
},
get() {
return ytInitialPlayerResponse;
}
});

return {
onFetchListener: (listener) => listeners.push(listener)
};
})();

{
const playerConfig = (args) => {
const adPlacementsParent = Util.getSingleObjectAndParentByKey(args, "adPlacements");
const adSlotsParent = Util.getSingleObjectAndParentByKey(args, "adSlots");
const playerAdsParent = Util.getSingleObjectAndParentByKey(args, "playerAds");

if (adPlacementsParent?.parent?.["adPlacements"]) {
delete adPlacementsParent.parent["adPlacements"];
}

if (adSlotsParent?.parent?.["adSlots"]) {
delete adSlotsParent.parent["adSlots"];
}

if (playerAdsParent?.parent?.["playerAds"]) {
delete playerAdsParent.parent["playerAds"];
}
};

OverrideFetch.onFetchListener(playerConfig);
OverrideHandleResponse.onHandleResponseListener(playerConfig);
}
25 changes: 25 additions & 0 deletions src/betterEmbedsYT/embedBypass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// partially adapted from https://greasyfork.org/en/scripts/465518-youtube-embed-whatever-wherever/code
// original script doesnt work anymore

function checkStatus() {
const ytcfg = window.ytcfg;
const player_response = JSON.parse(ytcfg.data_.PLAYER_VARS.embedded_player_response);

if (!player_response?.previewPlayabilityStatus?.status) return;
if (player_response.previewPlayabilityStatus.status === "OK") return;
location.reload();
}

function waitForConfig() {
if (window.ytcfg) {
checkStatus();
} else {
requestAnimationFrame(waitForConfig);
}
}

if (window.ytcfg) {
checkStatus();
} else {
waitForConfig();
}
14 changes: 14 additions & 0 deletions src/betterEmbedsYT/host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { BrowserWindow } from "electron";
import { webFrameMain } from "electron";
import embedBypass from "inline:./embedBypass.js";
import adblock from "inline:./adblock.js";

moonlightHost.events.on("window-created", (window: BrowserWindow) => {
window.webContents.on("did-frame-navigate", (event, url, code, status, main, pid, rid) => {
const frame = webFrameMain.fromId(pid, rid);
if (frame != null && url.includes("youtube.com/embed/")) {
frame.executeJavaScript(adblock);
frame.executeJavaScript(embedBypass);
}
});
});
3 changes: 3 additions & 0 deletions src/betterEmbedsYT/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Patch } from "@moonlight-mod/types";

export const patches: Patch[] = [];
24 changes: 24 additions & 0 deletions src/betterEmbedsYT/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
"id": "betterEmbedsYT",
"version": "1.0.0",
"meta": {
"name": "Better YouTube Embeds",
"tagline": "Bypass copyright blocks, block ads and trackers. Works with Watch Together.",
"description": "Ad blocking uses code from the [Iridium](https://github.com/ParticleCore/Iridium) web extension.",
"authors": ["Cynosphere"],
"tags": ["qol", "privacy"],
"source": "https://github.com/Cynosphere/moonlight-extensions"
},
"settings": {},
"apiLevel": 2,
"blocked": [
"https://play.google.com/log*",
"https://*.doubleclick.net/pagead*",
"https://*.youtube.com/youtubei/v1/log_event*",
"https://*.youtube.com/api/stats/qoe*",
"https://*.youtube.com/api/stats/atr*",
"https://*.youtube.com/pagead*",
"https://*.youtube.com/ptracking*"
]
}

0 comments on commit 6da54e2

Please sign in to comment.