Advertisement
Guest User

Untitled

a guest
May 26th, 2021
482
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.13 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 TrapCopyConf = (value) => new Proxy(value, {
  78. apply: (target, thisArg, argumentsList) => {
  79. if(argumentsList.length != 2) {
  80. return Reflect.apply(target, thisArg, argumentsList);
  81. }
  82.  
  83. try {
  84. let client = argumentsList[1].c;
  85. let clientVer = argumentsList[1].cver;
  86. if(client === "WEB_EMBEDDED_PLAYER") {
  87. debug("Patching WEB_EMBEDDED_PLAYER");
  88. argumentsList[1].c = "WEB";
  89. argumentsList[0].el = "detailpage";
  90. if(!clientVer.includes(".")) {
  91. debug("Patching client version");
  92. argumentsList[1].cver = "2." + clientVer;
  93. }
  94. }
  95. } catch (err) {
  96. debug("CopyConf error", err);
  97. } finally {
  98. return Reflect.apply(target, thisArg, argumentsList);
  99. }
  100. },
  101. });
  102.  
  103. const TrapConfigFetch = (value) => new Proxy(value, {
  104. apply: (target, thisArg, argumentsList) => {
  105. if(argumentsList.length == 0) {
  106. return Reflect.apply(target, thisArg, argumentsList);
  107. }
  108.  
  109. let key = argumentsList[0];
  110. let result = Reflect.apply(target, thisArg, argumentsList);
  111. //debug(key, result);
  112. switch (key) {
  113. case "INNERTUBE_CONTEXT":
  114. try {
  115. let client = result.client.clientName;
  116. let clientVer = result.client.clientVersion;
  117.  
  118. if(client === "WEB_EMBEDDED_PLAYER") {
  119. result.client.clientName = "WEB";
  120. if(!clientVer.includes(".")) {
  121. debug("Patching client version");
  122. result.client.clientVersion = "2." + clientVer;
  123. }
  124. }
  125. } catch (err) {
  126. debug("ConfigFetch error", err);
  127. } finally {
  128. return result;
  129. }
  130. //case "INNERTUBE_CONTEXT_CLIENT_NAME":
  131. // if (result === 56) {
  132. // // YouTube uses this header to determine if you're watching from an embedded video player,
  133. // // we have to strip it to force embeds to work even when they're disabled by the author.
  134. // debug("Stripping INNERTUBE_CONTEXT_CLIENT_NAME");
  135. // return;
  136. // }
  137. // break;
  138. }
  139.  
  140. return result;
  141. },
  142. });
  143.  
  144. const TrapUpdateVideoInfo = (value) => new Proxy(value, {
  145. apply: (target, thisArg, argumentsList) => {
  146. (() => {
  147. if(argumentsList.length !== 3) {
  148. return;
  149. }
  150.  
  151. let videoInfo = argumentsList[1];
  152. let playerResponse = GetPlayerResponse(videoInfo);
  153. if(typeof playerResponse === "undefined") {
  154. return;
  155. }
  156.  
  157. if(typeof playerResponse === "string") {
  158. playerResponse = JSON.parse(playerResponse);
  159. delete videoInfo.player_response;
  160. delete videoInfo.embedded_player_response;
  161. }
  162.  
  163. PatchPlayerResponse(playerResponse);
  164. })();
  165. debug("TrapUpdateVideoInfo", thisArg, argumentsList);
  166. return Reflect.apply(target, thisArg, argumentsList);
  167. },
  168. });
  169.  
  170. const TrapYTPlayer = (value) => {
  171. const VideoConstructorFuncRegex = /this.adaptiveFormats="";/;
  172. const ConfigFetchFuncRegex = /^function\(a,b\)\{return a in /;
  173. const CopyConfFuncRegex = /^function\(a,b\)\{for\(var c in b\)a\[c\]=b\[c\]\}$/;
  174. const UpdateVideoInfoRegex = /a.errorCode=null/;
  175.  
  176. let FoundVideoConstructor = false;
  177. let FoundConfigFetch = false;
  178. let FoundCopyConf = false;
  179. let FoundUpdateVideoInfo = false;
  180.  
  181. return new Proxy(value, {
  182. defineProperty: (target, property, descriptor) => {
  183. (() => {
  184. if(typeof descriptor.value !== "function") {
  185. return;
  186. }
  187.  
  188. if(!FoundUpdateVideoInfo) {
  189. if(UpdateVideoInfoRegex.test(descriptor.value.toString())) {
  190. // UpdateVideoInfo is used for embeded videos, we need to trap
  191. // it to enable DVR on embeds.
  192. debug("Found UpdateVideoInfo func", property, descriptor.value);
  193. descriptor.value = TrapUpdateVideoInfo(descriptor.value);
  194. FoundUpdateVideoInfo = true;
  195. return;
  196. }
  197. }
  198.  
  199. if(!FoundCopyConf) {
  200. if(CopyConfFuncRegex.test(descriptor.value.toString())) {
  201. // CopyConfFunc is used to copy configuration data between objects,
  202. // we patch it and change some data as it's copied
  203. debug("Found CopyConf func", property, descriptor.value);
  204. descriptor.value = TrapCopyConf(descriptor.value);
  205. FoundCopyConf = true;
  206. return;
  207. }
  208. }
  209.  
  210. if(!FoundVideoConstructor) {
  211. if(VideoConstructorFuncRegex.test(descriptor.value.toString())) {
  212. // VideoConstructor func is the constructor for videos,
  213. // we use it to patch some data when new videos are loaded.
  214. debug("Found VideoConstructor func", property, descriptor.value);
  215. descriptor.value = TrapVideoConstructor(descriptor.value);
  216. FoundVideoConstructor = true;
  217. return;
  218. }
  219. }
  220.  
  221. if(!FoundConfigFetch) {
  222. if(ConfigFetchFuncRegex.test(descriptor.value.toString())) {
  223. // ConfigFetch func is used to fetch configuration data used in HTTP headers,
  224. // we need to strip some of them to force embeds to work.
  225. debug("Found ConfigFetch func", property, descriptor);
  226. descriptor.value = TrapConfigFetch(descriptor.value);
  227. FoundConfigFetch = true;
  228. return;
  229. }
  230. }
  231. })();
  232.  
  233. return Reflect.defineProperty(target, property, descriptor);
  234. },
  235. });
  236. }
  237.  
  238. debug("Script start");
  239.  
  240. Object.defineProperty(window, "_yt_player", {
  241. value: TrapYTPlayer({}),
  242. });
  243. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement