Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name YTBetter
- // @namespace YTBetter
- // @match https://*.youtube.com/*
- // @run-at document-start
- // @grant none
- // @version 1.1
- // @author トワ…
- // @description Patches YouTube to bypass some limitations
- // ==/UserScript==
- (() => {
- const _DEBUG = false;
- const debug = (...msg) => {
- if(_DEBUG) {
- console.log("[YTBetter]", ...msg);
- }
- }
- const PatchPlayerResponse = (playerResponse) => {
- try {
- // Patch to allow DVR to work on all streams
- if(playerResponse.videoDetails) {
- playerResponse.videoDetails.isLiveDvrEnabled = true;
- }
- } catch (err) {
- debug("Failed to patch playerResponse", err);
- }
- };
- const GetPlayerResponse = (videoInfo) => {
- return videoInfo.raw_player_response || videoInfo.embedded_player_response || videoInfo.player_response;
- };
- const TrapVideoConstructor = (value) => new Proxy(value, {
- construct: (target, argumentsList, newTarget) => {
- debug("Trapped SE constructor with arguments", target, argumentsList, newTarget);
- (() => {
- if(argumentsList.length !== 2) {
- return;
- }
- let videoInfo = argumentsList[1];
- let playerResponse = GetPlayerResponse(videoInfo);
- if(typeof playerResponse === "undefined") {
- return;
- }
- if(typeof playerResponse === "string") {
- playerResponse = JSON.parse(playerResponse);
- delete videoInfo.player_response;
- delete videoInfo.embedded_player_response;
- }
- PatchPlayerResponse(playerResponse);
- if(playerResponse.videoFlags && !playerResponse.videoFlags.playableInEmbed) {
- try {
- // We need to patch these to force embeds to work
- //argumentsList[0].deviceParams.c = "WEB";
- for(const key in argumentsList[0]) {
- if(argumentsList[0][key] === "embedded") {
- debug("Patching embedded key", key);
- argumentsList[0][key] = "detailpage";
- }
- }
- } catch (err) {
- debug("Failed to patch embeds", err);
- }
- }
- })();
- return Reflect.construct(target, argumentsList, newTarget);
- },
- });
- const TrapNonembedVideoConstructor = (value) => new Proxy(value, {
- construct: (target, argumentsList, newTarget) => {
- debug("Trapped Nonembed constructor with arguments", target, argumentsList, newTarget);
- (() => {
- let player = argumentsList[8];
- player.allowLiveDvr = true
- })();
- return Reflect.construct(target, argumentsList, newTarget);
- },
- });
- const TrapCopyConf = (value) => new Proxy(value, {
- apply: (target, thisArg, argumentsList) => {
- if(argumentsList.length != 2) {
- return Reflect.apply(target, thisArg, argumentsList);
- }
- try {
- let client = argumentsList[1].c;
- let clientVer = argumentsList[1].cver;
- if(client === "WEB_EMBEDDED_PLAYER") {
- debug("Patching WEB_EMBEDDED_PLAYER");
- argumentsList[1].c = "WEB";
- argumentsList[0].el = "detailpage";
- if(!clientVer.includes(".")) {
- debug("Patching client version");
- argumentsList[1].cver = "2." + clientVer;
- }
- }
- } catch (err) {
- debug("CopyConf error", err);
- } finally {
- return Reflect.apply(target, thisArg, argumentsList);
- }
- },
- });
- const TrapConfigFetch = (value) => new Proxy(value, {
- apply: (target, thisArg, argumentsList) => {
- if(argumentsList.length == 0) {
- return Reflect.apply(target, thisArg, argumentsList);
- }
- let key = argumentsList[0];
- let result = Reflect.apply(target, thisArg, argumentsList);
- //debug(key, result);
- switch (key) {
- case "INNERTUBE_CONTEXT":
- try {
- let client = result.client.clientName;
- let clientVer = result.client.clientVersion;
- if(client === "WEB_EMBEDDED_PLAYER") {
- result.client.clientName = "WEB";
- if(!clientVer.includes(".")) {
- debug("Patching client version");
- result.client.clientVersion = "2." + clientVer;
- }
- }
- } catch (err) {
- debug("ConfigFetch error", err);
- } finally {
- return result;
- }
- //case "INNERTUBE_CONTEXT_CLIENT_NAME":
- // if (result === 56) {
- // // YouTube uses this header to determine if you're watching from an embedded video player,
- // // we have to strip it to force embeds to work even when they're disabled by the author.
- // debug("Stripping INNERTUBE_CONTEXT_CLIENT_NAME");
- // return;
- // }
- // break;
- }
- return result;
- },
- });
- const TrapUpdateVideoInfo = (value) => new Proxy(value, {
- apply: (target, thisArg, argumentsList) => {
- (() => {
- if(argumentsList.length !== 3) {
- return;
- }
- let videoInfo = argumentsList[1];
- let playerResponse = GetPlayerResponse(videoInfo);
- if(typeof playerResponse === "undefined") {
- return;
- }
- if(typeof playerResponse === "string") {
- playerResponse = JSON.parse(playerResponse);
- delete videoInfo.player_response;
- delete videoInfo.embedded_player_response;
- }
- PatchPlayerResponse(playerResponse);
- })();
- debug("TrapUpdateVideoInfo", thisArg, argumentsList);
- return Reflect.apply(target, thisArg, argumentsList);
- },
- });
- const TrapYTPlayer = (value) => {
- const VideoConstructorFuncRegex = /this.allowLiveDvr=this./;
- const VideoNonembedConstructorFuncRegex = /function\(a,b,c,d,e,f,h,l,m,n\){m=void/;
- const ConfigFetchFuncRegex = /^function\(a,b\)\{return a in /;
- const CopyConfFuncRegex = /^function\(a,b\)\{for\(var c in b\)a\[c\]=b\[c\]\}$/;
- const UpdateVideoInfoRegex = /a.errorCode=null/;
- let FoundVideoConstructor = false;
- let FoundNonembedVideoConstructor = false;
- let FoundConfigFetch = false;
- let FoundCopyConf = false;
- let FoundUpdateVideoInfo = false;
- return new Proxy(value, {
- defineProperty: (target, property, descriptor) => {
- (() => {
- if(typeof descriptor.value !== "function") {
- return;
- }
- if(!FoundUpdateVideoInfo) {
- if(UpdateVideoInfoRegex.test(descriptor.value.toString())) {
- // UpdateVideoInfo is used for embeded videos, we need to trap
- // it to enable DVR on embeds.
- debug("Found UpdateVideoInfo func", property, descriptor.value);
- descriptor.value = TrapUpdateVideoInfo(descriptor.value);
- FoundUpdateVideoInfo = true;
- return;
- }
- }
- if(!FoundCopyConf) {
- if(CopyConfFuncRegex.test(descriptor.value.toString())) {
- // CopyConfFunc is used to copy configuration data between objects,
- // we patch it and change some data as it's copied
- debug("Found CopyConf func", property, descriptor.value);
- descriptor.value = TrapCopyConf(descriptor.value);
- FoundCopyConf = true;
- return;
- }
- }
- if(!FoundVideoConstructor) {
- if(VideoConstructorFuncRegex.test(descriptor.value.toString())) {
- // VideoConstructor func is the constructor for videos,
- // we use it to patch some data when new videos are loaded.
- debug("Found VideoConstructor func", property, descriptor.value);
- descriptor.value = TrapVideoConstructor(descriptor.value);
- FoundVideoConstructor = true;
- return;
- }
- }
- if(!FoundNonembedVideoConstructor) {
- if(VideoNonembedConstructorFuncRegex.test(descriptor.value.toString())) {
- // VideoConstructor func is the constructor for videos,
- // we use it to patch some data when new videos are loaded.
- debug("Found NonembedVideoConstructor func", property, descriptor.value);
- descriptor.value = TrapNonembedVideoConstructor(descriptor.value);
- FoundNonembedVideoConstructor = true;
- return;
- }
- }
- if(!FoundConfigFetch) {
- if(ConfigFetchFuncRegex.test(descriptor.value.toString())) {
- // ConfigFetch func is used to fetch configuration data used in HTTP headers,
- // we need to strip some of them to force embeds to work.
- debug("Found ConfigFetch func", property, descriptor);
- descriptor.value = TrapConfigFetch(descriptor.value);
- FoundConfigFetch = true;
- return;
- }
- }
- })();
- return Reflect.defineProperty(target, property, descriptor);
- },
- });
- }
- Object.defineProperty(window, "_yt_player", {
- value: TrapYTPlayer({}),
- });
- })();
Add Comment
Please, Sign In to add comment