Advertisement
Guest User

Untitled

a guest
Dec 13th, 2021
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.18 KB | None | 0 0
  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. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement