Change YouTube Leftbar Subscription Links To Channel/User Video Page

Change YouTube leftbar's subscription links to channel/user video page. This script can optionally also move the links to top of the list if it has new uploaded videos. Both features can be enabled/disabled. For new YouTube layout only.

当前为 2021-01-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Change YouTube Leftbar Subscription Links To Channel/User Video Page
  3. // @namespace ChangeYouTubeLeftbarSubscriptionLinksToChannelUserVideoPage
  4. // @version 1.1.14
  5. // @license AGPL v3
  6. // @author jcunews
  7. // @description Change YouTube leftbar's subscription links to channel/user video page. This script can optionally also move the links to top of the list if it has new uploaded videos. Both features can be enabled/disabled. For new YouTube layout only.
  8. // @match *://www.youtube.com/*
  9. // @grant none
  10. // @run-at document-start
  11. // ==/UserScript==
  12.  
  13. (yigd => {
  14.  
  15. //===== Configuration Start ===
  16.  
  17. var changeLinksURL = true;
  18. var moveLinksToTop = true;
  19.  
  20. //===== Configuration End ===
  21.  
  22. function changeUrl(url, m) {
  23. if (m = url.match(/^\/feed\/subscriptions\/([^\/]+)$/)) {
  24. return "/channel/" + m[1] + "/videos";
  25. } else return url + "/videos";
  26. }
  27.  
  28. function updateEndpoint(ep) {
  29. ep.commandMetadata.webCommandMetadata.url = changeUrl(ep.commandMetadata.webCommandMetadata.url);
  30. if (ep.webNavigationEndpointData) ep.webNavigationEndpointData.url = changeUrl(ep.webNavigationEndpointData.url);
  31. (function waitGD(gd) {
  32. if ((gd = window["guide-renderer"].__data) && (gd = gd.data) && (gd = gd.items) && (gd = gd.reduce(
  33. (r, v) => {
  34. if (!r && (v = v.guideSubscriptionsSectionRenderer) && (v = v.items)) {
  35. r = v.reduce((r, e) => {
  36. if (e.guideCollapsibleEntryRenderer) {
  37. e.guideCollapsibleEntryRenderer.expandableItems.forEach(t => {
  38. if (!t.guideEntryRenderer.icon || (t.guideEntryRenderer.icon.iconType !== "ADD_CIRCLE")) r.push(t.guideEntryRenderer.navigationEndpoint)
  39. });
  40. } else r.push(e.guideEntryRenderer.navigationEndpoint);
  41. return r;
  42. }, []);
  43. }
  44. return r;
  45. }, null
  46. ))) {
  47. fetch(location.protocol + "//" + location.host + ep.commandMetadata.webCommandMetadata.url, {credentials: "omit"}).then(r => r.text().then((h, o) => {
  48. if ((h = h.match(/window\["ytInitialData"\] = (\{.*?)\};\n/)) && (h = JSON.parse(h[1] + "}").contents.twoColumnBrowseResultsRenderer.tabs)) {
  49. h.some(a => {
  50. if ((/^\/[^\/]+\/[^\/]+\/videos$/).test((a = a.tabRenderer.endpoint).commandMetadata.webCommandMetadata.url)) {
  51. ep.browseEndpoint.params = a.browseEndpoint.params;
  52. ep.commandMetadata.webCommandMetadata.url = a.commandMetadata.webCommandMetadata.url;
  53. if (ep.webNavigationEndpointData) ep.webNavigationEndpointData.url = a.commandMetadata.webCommandMetadata.url;
  54. gd.some(d => {
  55. if (d.browseEndpoint.browseId === a.browseEndpoint.browseId) {
  56. d.browseEndpoint.params = a.browseEndpoint.params;
  57. d.commandMetadata.webCommandMetadata.url = a.commandMetadata.webCommandMetadata.url;
  58. if (d.webNavigationEndpointData) d.webNavigationEndpointData.url = a.commandMetadata.webCommandMetadata.url;
  59. return true;
  60. }
  61. });
  62. return true;
  63. }
  64. })
  65. }
  66. }))
  67. } else setTimeout(waitGD, 100)
  68. })()
  69. }
  70.  
  71. function patchGuide(guide, z) {
  72. if (guide && guide.items && !guide.cysl_done) try {
  73. guide.cysl_done = 1;
  74. guide.items.forEach((v, vc, l, w, c, i) => {
  75. if (v.guideSubscriptionsSectionRenderer) {
  76. //change links' URL
  77. if (changeLinksURL) {
  78. v.guideSubscriptionsSectionRenderer.items.forEach(w => {
  79. if (w.guideCollapsibleEntryRenderer) {
  80. w.guideCollapsibleEntryRenderer.expandableItems.forEach(x => {
  81. if (x.guideEntryRenderer.badges && (x = x.guideEntryRenderer.navigationEndpoint)) updateEndpoint(x);
  82. });
  83. } else if (w = w.guideEntryRenderer.navigationEndpoint) updateEndpoint(w);
  84. });
  85. }
  86. //move links with new uploads to top
  87. if (moveLinksToTop) {
  88. v = v.guideSubscriptionsSectionRenderer.items;
  89. vc = v.length - 1;
  90. w = v[vc].guideCollapsibleEntryRenderer;
  91. l = v.splice(0, vc); //main links to list1
  92. c = -1;
  93. if (w) { //collapsed links to list1
  94. (w = w.expandableItems).some((e, i) => {
  95. if (!e.guideEntryRenderer.badges) {
  96. c = i;
  97. return true;
  98. }
  99. });
  100. l.push.apply(l, w.splice(0, c));
  101. }
  102. c = []; //new links to main links. c = collapsed new links in list2
  103. for (i = l.length - 1; i >= 0; i--) {
  104. if (l[i].guideEntryRenderer.count || (l[i].guideEntryRenderer.presentationStyle === "GUIDE_ENTRY_PRESENTATION_STYLE_NEW_CONTENT")) {
  105. if (vc--) {
  106. v.unshift(l.splice(i, 1)[0]);
  107. } else c.unshift(l.splice(i, 1)[0]);
  108. }
  109. }
  110. c.push.apply(c, l); //list1 to list2
  111. if (vc) { //fill up main links from list2
  112. l = c.splice(0, vc);
  113. l.unshift.apply(l, [v.length - 1, 0]);
  114. v.splice.apply(v, l);
  115. }
  116. if (w) w.unshift.apply(w, c);
  117. }
  118. }
  119. });
  120. return true;
  121. } catch(z) {}
  122. return false;
  123. }
  124.  
  125. Object.defineProperty(window, "ytInitialGuideData", {
  126. get(v) {
  127. return yigd;
  128. },
  129. set(v) {
  130. delete window.ytInitialGuideData;
  131. patchGuide(v);
  132. return yigd = v;
  133. }
  134. });
  135.  
  136. JSON.parse_cylslcvp = JSON.parse;
  137. JSON.parse = function(txt) {
  138. var res = JSON.parse_cylslcvp.apply(this, arguments);
  139. if ((/\/youtubei\/v1\/guide\?/).test(JSON.url_cylslcvp)) patchGuide(res);
  140. return res;
  141. };
  142.  
  143. var fetch_ = window.fetch;
  144. window.fetch = function(opts) {
  145. var fres = fetch_.apply(this, arguments), fthen = fres.then;
  146. if ((/\/youtubei\/v1\/guide\?/).test(opts.url)) {
  147. JSON.url_cylslcvp = opts.url;
  148. fres.then = function(fn) {
  149. var fthenfn = fn;
  150. fn = function(fresp) {
  151. var frjson = fresp.json;
  152. fresp.json = function() {
  153. var jres = frjson.apply(this, arguments), jthen = jres.then;
  154. jres.then = function(jfn) {
  155. var jthenfn = jfn;
  156. jfn = function(jresp) {
  157. patchGuide(jresp);
  158. console.log(jresp);
  159. return jthenfn.apply(this, arguments);
  160. };
  161. return jthen.apply(this, arguments);
  162. };
  163. return jres;
  164. }
  165. return fthenfn.apply(this, arguments);
  166. };
  167. return fthen.apply(this, arguments);
  168. };
  169. } else JSON.url_cylslcvp = "";
  170. return fres;
  171. };
  172. var ht = 0;
  173. (function chkSpf() {
  174. clearTimeout(ht);
  175. if (window.spf && spf.request && !spf.request_cysl) {
  176. spf.request_cysl = spf.request;
  177. spf.request = function(a, b) {
  178. if (b && b.onDone) {
  179. var onDone_ = b.onDone;
  180. b.onDone = function(response) {
  181. if (response && (/\/guide_ajax\?/).test(response.url) && response.response && response.response.response) {
  182. patchGuide(response.response.response);
  183. }
  184. return onDone_.apply(this, arguments);
  185. };
  186. return this.request_cysl.apply(this, arguments);
  187. }
  188. };
  189. return;
  190. }
  191. ht = setTimeout(chkSpf, 0);
  192. })();
  193. addEventListener("click", (ev, a) => {
  194. if ((a = ev.target) && a.matches && a.matches("ytd-guide-section-renderer:nth-child(2) ytd-guide-entry-renderer *")) {
  195. ev.stopImmediatePropagation();
  196. ev.preventDefault();
  197. while (a.tagName !== "A") a = a.parentNode;
  198. a = a.__dataHost.__data.data.navigationEndpoint;
  199. nav.navigate({
  200. commandMetadata: {webCommandMetadata: {url: a.commandMetadata.webCommandMetadata.url, webPageType: "WEB_PAGE_TYPE_CHANNEL"}},
  201. browseEndpoint: a.browseEndpoint
  202. }, false);
  203. }
  204. }, true);
  205. addEventListener("load", () => {
  206. clearTimeout(ht);
  207. });
  208.  
  209. })();