Advertisement
Guest User

YTBetter - working 9/25/2021

a guest
Sep 25th, 2021
526
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        YTBetter
  3. // @namespace   YTBetter
  4. // @match       https://*.youtube.com/*
  5. // @run-at      document-start
  6. // @grant       none
  7. // @version     1.1
  8. // @author      トワ…
  9. // @description Patches YouTube to bypass some limitations
  10. // ==/UserScript==
  11.  
  12. (() => {
  13.   const _DEBUG = false;
  14.   const debug = (...msg) => {
  15.     if(_DEBUG) {
  16.       console.log("[YTBetter]", ...msg);
  17.     }
  18.   }
  19.  
  20.   const PatchPlayerResponse = (playerResponse) => {
  21.     try {
  22.       // Patch to allow DVR to work on all streams
  23.       if(playerResponse.videoDetails) {
  24.         playerResponse.videoDetails.isLiveDvrEnabled = true;
  25.       }
  26.     } catch (err) {
  27.       debug("Failed to patch playerResponse", err);
  28.     }
  29.   };
  30.  
  31.   const GetPlayerResponse = (videoInfo) => {
  32.     return videoInfo.raw_player_response || videoInfo.embedded_player_response || videoInfo.player_response;
  33.   };
  34.  
  35.   const TrapVideoConstructor = (value) => new Proxy(value, {
  36.     construct: (target, argumentsList, newTarget) => {
  37.       debug("Trapped SE constructor with arguments", target, argumentsList, newTarget);
  38.  
  39.       (() => {
  40.         if(argumentsList.length !== 2) {
  41.           return;
  42.         }
  43.  
  44.         let videoInfo = argumentsList[1];
  45.         let playerResponse = GetPlayerResponse(videoInfo);
  46.         if(typeof playerResponse === "undefined") {
  47.           return;
  48.         }
  49.  
  50.         if(typeof playerResponse === "string") {
  51.           playerResponse = JSON.parse(playerResponse);
  52.           delete videoInfo.player_response;
  53.           delete videoInfo.embedded_player_response;
  54.         }
  55.  
  56.         PatchPlayerResponse(playerResponse);
  57.         if(playerResponse.videoFlags && !playerResponse.videoFlags.playableInEmbed) {
  58.           try {
  59.             // We need to patch these to force embeds to work
  60.             //argumentsList[0].deviceParams.c = "WEB";
  61.             for(const key in argumentsList[0]) {
  62.               if(argumentsList[0][key] === "embedded") {
  63.                 debug("Patching embedded key", key);
  64.                 argumentsList[0][key] = "detailpage";
  65.               }
  66.             }
  67.           } catch (err) {
  68.             debug("Failed to patch embeds", err);
  69.           }
  70.         }
  71.       })();
  72.  
  73.       return Reflect.construct(target, argumentsList, newTarget);
  74.     },
  75.   });
  76.  
  77.   const TrapNonembedVideoConstructor = (value) => new Proxy(value, {
  78.     construct: (target, argumentsList, newTarget) => {
  79.       debug("Trapped Nonembed constructor with arguments", target, argumentsList, newTarget);
  80.  
  81.       (() => {
  82.         let player = argumentsList[8];
  83.         player.allowLiveDvr = true
  84.       })();
  85.  
  86.       return Reflect.construct(target, argumentsList, newTarget);
  87.     },
  88.   });
  89.  
  90.   const TrapCopyConf = (value) => new Proxy(value, {
  91.     apply: (target, thisArg, argumentsList) => {
  92.       if(argumentsList.length != 2) {
  93.         return Reflect.apply(target, thisArg, argumentsList);
  94.       }
  95.  
  96.       try {
  97.         let client = argumentsList[1].c;
  98.         let clientVer = argumentsList[1].cver;
  99.         if(client === "WEB_EMBEDDED_PLAYER") {
  100.           debug("Patching WEB_EMBEDDED_PLAYER");
  101.           argumentsList[1].c = "WEB";
  102.           argumentsList[0].el = "detailpage";
  103.           if(!clientVer.includes(".")) {
  104.             debug("Patching client version");
  105.             argumentsList[1].cver = "2." + clientVer;
  106.           }
  107.         }
  108.       } catch (err) {
  109.         debug("CopyConf error", err);
  110.       } finally {
  111.         return Reflect.apply(target, thisArg, argumentsList);
  112.       }
  113.     },
  114.   });
  115.  
  116.   const TrapConfigFetch = (value) => new Proxy(value, {
  117.     apply: (target, thisArg, argumentsList) => {
  118.       if(argumentsList.length == 0) {
  119.         return Reflect.apply(target, thisArg, argumentsList);
  120.       }
  121.  
  122.       let key = argumentsList[0];
  123.       let result = Reflect.apply(target, thisArg, argumentsList);
  124.       //debug(key, result);
  125.       switch (key) {
  126.         case "INNERTUBE_CONTEXT":
  127.           try {
  128.             let client = result.client.clientName;
  129.             let clientVer = result.client.clientVersion;
  130.  
  131.             if(client === "WEB_EMBEDDED_PLAYER") {
  132.               result.client.clientName = "WEB";
  133.               if(!clientVer.includes(".")) {
  134.                 debug("Patching client version");
  135.                 result.client.clientVersion = "2." + clientVer;
  136.               }
  137.             }
  138.           } catch (err) {
  139.             debug("ConfigFetch error", err);
  140.           } finally {
  141.             return result;
  142.           }
  143.           //case "INNERTUBE_CONTEXT_CLIENT_NAME":
  144.           //  if (result === 56) {
  145.           //    // YouTube uses this header to determine if you're watching from an embedded video player,
  146.           //    // we have to strip it to force embeds to work even when they're disabled by the author.
  147.           //    debug("Stripping INNERTUBE_CONTEXT_CLIENT_NAME");
  148.           //    return;
  149.           //  }
  150.           //  break;
  151.       }
  152.  
  153.       return result;
  154.     },
  155.   });
  156.  
  157.   const TrapUpdateVideoInfo = (value) => new Proxy(value, {
  158.     apply: (target, thisArg, argumentsList) => {
  159.       (() => {
  160.         if(argumentsList.length !== 3) {
  161.           return;
  162.         }
  163.  
  164.         let videoInfo = argumentsList[1];
  165.         let playerResponse = GetPlayerResponse(videoInfo);
  166.         if(typeof playerResponse === "undefined") {
  167.           return;
  168.         }
  169.  
  170.         if(typeof playerResponse === "string") {
  171.           playerResponse = JSON.parse(playerResponse);
  172.           delete videoInfo.player_response;
  173.           delete videoInfo.embedded_player_response;
  174.         }
  175.  
  176.         PatchPlayerResponse(playerResponse);
  177.       })();
  178.       debug("TrapUpdateVideoInfo", thisArg, argumentsList);
  179.       return Reflect.apply(target, thisArg, argumentsList);
  180.     },
  181.   });
  182.  
  183.   const TrapYTPlayer = (value) => {
  184.     const VideoConstructorFuncRegex = /this.allowLiveDvr=this./;
  185.     const VideoNonembedConstructorFuncRegex = /function\(a,b,c,d,e,f,h,l,m,n\){m=void/;
  186.     const ConfigFetchFuncRegex = /^function\(a,b\)\{return a in /;
  187.     const CopyConfFuncRegex = /^function\(a,b\)\{for\(var c in b\)a\[c\]=b\[c\]\}$/;
  188.     const UpdateVideoInfoRegex = /a.errorCode=null/;
  189.  
  190.     let FoundVideoConstructor = false;
  191.     let FoundNonembedVideoConstructor = false;
  192.     let FoundConfigFetch = false;
  193.     let FoundCopyConf = false;
  194.     let FoundUpdateVideoInfo = false;
  195.  
  196.     return new Proxy(value, {
  197.       defineProperty: (target, property, descriptor) => {
  198.         (() => {
  199.           if(typeof descriptor.value !== "function") {
  200.             return;
  201.           }
  202.           if(!FoundUpdateVideoInfo) {
  203.             if(UpdateVideoInfoRegex.test(descriptor.value.toString())) {
  204.               // UpdateVideoInfo is used for embeded videos, we need to trap
  205.               // it to enable DVR on embeds.
  206.               debug("Found UpdateVideoInfo func", property, descriptor.value);
  207.               descriptor.value = TrapUpdateVideoInfo(descriptor.value);
  208.               FoundUpdateVideoInfo = true;
  209.               return;
  210.             }
  211.           }
  212.  
  213.           if(!FoundCopyConf) {
  214.             if(CopyConfFuncRegex.test(descriptor.value.toString())) {
  215.               // CopyConfFunc is used to copy configuration data between objects,
  216.               // we patch it and change some data as it's copied
  217.               debug("Found CopyConf func", property, descriptor.value);
  218.               descriptor.value = TrapCopyConf(descriptor.value);
  219.               FoundCopyConf = true;
  220.               return;
  221.             }
  222.           }
  223.  
  224.           if(!FoundVideoConstructor) {
  225.             if(VideoConstructorFuncRegex.test(descriptor.value.toString())) {
  226.               // VideoConstructor func is the constructor for videos,
  227.               // we use it to patch some data when new videos are loaded.
  228.               debug("Found VideoConstructor func", property, descriptor.value);
  229.               descriptor.value = TrapVideoConstructor(descriptor.value);
  230.               FoundVideoConstructor = true;
  231.               return;
  232.             }
  233.           }
  234.  
  235.           if(!FoundNonembedVideoConstructor) {
  236.             if(VideoNonembedConstructorFuncRegex.test(descriptor.value.toString())) {
  237.               // VideoConstructor func is the constructor for videos,
  238.               // we use it to patch some data when new videos are loaded.
  239.               debug("Found NonembedVideoConstructor func", property, descriptor.value);
  240.               descriptor.value = TrapNonembedVideoConstructor(descriptor.value);
  241.               FoundNonembedVideoConstructor = true;
  242.               return;
  243.             }
  244.           }
  245.  
  246.           if(!FoundConfigFetch) {
  247.             if(ConfigFetchFuncRegex.test(descriptor.value.toString())) {
  248.               // ConfigFetch func is used to fetch configuration data used in HTTP headers,
  249.               // we need to strip some of them to force embeds to work.
  250.               debug("Found ConfigFetch func", property, descriptor);
  251.               descriptor.value = TrapConfigFetch(descriptor.value);
  252.               FoundConfigFetch = true;
  253.               return;
  254.             }
  255.           }
  256.         })();
  257.  
  258.         return Reflect.defineProperty(target, property, descriptor);
  259.       },
  260.     });
  261.   }
  262.  
  263.   Object.defineProperty(window, "_yt_player", {
  264.     value: TrapYTPlayer({}),
  265.   });
  266. })();
  267.  
  268.  
  269.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement