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.

当前为 2020-04-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Change YouTube Leftbar Subscription Links To Channel/User Video Page
  3. // @namespace ChangeYouTubeLeftbarSubscriptionLinksToChannelUserVideoPage
  4. // @version 1.1.9
  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 patchGuide(guide) {
  29. if (guide && !guide.cysl_done) {
  30. guide.cysl_done = 1;
  31. guide.items.forEach((v, vc, l, w, c, i) => {
  32. if (v.guideSubscriptionsSectionRenderer) {
  33. //change links' URL
  34. if (changeLinksURL) {
  35. v.guideSubscriptionsSectionRenderer.items.forEach(w => {
  36. if (w.guideCollapsibleEntryRenderer) {
  37. w.guideCollapsibleEntryRenderer.expandableItems.forEach(x => {
  38. if (x.guideEntryRenderer.badges && (x = x.guideEntryRenderer.navigationEndpoint)) {
  39. x.commandMetadata.webCommandMetadata.url = changeUrl(x.commandMetadata.webCommandMetadata.url);
  40. if (x.webNavigationEndpointData) x.webNavigationEndpointData.url = changeUrl(x.webNavigationEndpointData.url);
  41. }
  42. });
  43. } else if (w = w.guideEntryRenderer.navigationEndpoint) {
  44. w.commandMetadata.webCommandMetadata.url = changeUrl(w.commandMetadata.webCommandMetadata.url);
  45. if (w.webNavigationEndpointData) w.webNavigationEndpointData.url = changeUrl(w.webNavigationEndpointData.url);
  46. }
  47. });
  48. }
  49. //move links with new uploads to top
  50. if (moveLinksToTop) {
  51. v = v.guideSubscriptionsSectionRenderer.items;
  52. vc = v.length - 1;
  53. w = v[vc].guideCollapsibleEntryRenderer;
  54. l = v.splice(0, vc); //main links to list1
  55. c = -1;
  56. if (w) { //collapsed links to list1
  57. (w = w.expandableItems).some((e, i) => {
  58. if (!e.guideEntryRenderer.badges) {
  59. c = i;
  60. return true;
  61. }
  62. });
  63. l.push.apply(l, w.splice(0, c));
  64. }
  65. c = []; //new links to main links. c = collapsed new links in list2
  66. for (i = l.length - 1; i >= 0; i--) {
  67. if (l[i].guideEntryRenderer.count || (l[i].guideEntryRenderer.presentationStyle === "GUIDE_ENTRY_PRESENTATION_STYLE_NEW_CONTENT")) {
  68. if (vc--) {
  69. v.unshift(l.splice(i, 1)[0]);
  70. } else c.unshift(l.splice(i, 1)[0]);
  71. }
  72. }
  73. c.push.apply(c, l); //list1 to list2
  74. if (vc) { //fill up main links from list2
  75. l = c.splice(0, vc);
  76. l.unshift.apply(l, [v.length - 1, 0]);
  77. v.splice.apply(v, l);
  78. }
  79. if (w) w.unshift.apply(w, c);
  80. }
  81. }
  82. });
  83. return true;
  84. }
  85. return false;
  86. }
  87.  
  88. Object.defineProperty(window, "ytInitialGuideData", {
  89. get(v) {
  90. return yigd;
  91. },
  92. set(v) {
  93. delete window.ytInitialGuideData;
  94. patchGuide(v);
  95. return yigd = v;
  96. }
  97. });
  98.  
  99. var fetch_ = window.fetch;
  100. window.fetch = function(opts) {
  101. var fres = fetch_.apply(this, arguments), fthen = fres.then;
  102. if ((/\/youtubei\/v1\/guide\?/).test(opts.url)) {
  103. fres.then = function(fn) {
  104. var fthenfn = fn;
  105. fn = function(fresp) {
  106. var frjson = fresp.json;
  107. fresp.json = function() {
  108. var jres = frjson.apply(this, arguments), jthen = jres.then;
  109. jres.then = function(jfn) {
  110. var jthenfn = jfn;
  111. jfn = function(jresp) {
  112. patchGuide(jresp);
  113. return jthenfn.apply(this, arguments);
  114. };
  115. return jthen.apply(this, arguments);
  116. };
  117. return jres;
  118. }
  119. return fthenfn.apply(this, arguments);
  120. };
  121. return fthen.apply(this, arguments);
  122. };
  123. }
  124. return fres;
  125. };
  126. var ht = 0;
  127. (function chkSpf() {
  128. clearTimeout(ht);
  129. if (window.spf && spf.request && !spf.request_cysl) {
  130. spf.request_cysl = spf.request;
  131. spf.request = function(a, b) {
  132. if (b && b.onDone) {
  133. var onDone_ = b.onDone;
  134. b.onDone = function(response) {
  135. if (response && (/\/guide_ajax\?/).test(response.url) && response.response && response.response.response) {
  136. patchGuide(response.response.response);
  137. }
  138. return onDone_.apply(this, arguments);
  139. };
  140. return this.request_cysl.apply(this, arguments);
  141. }
  142. };
  143. return;
  144. }
  145. ht = setTimeout(chkSpf, 0);
  146. })();
  147.  
  148. addEventListener("load", () => {
  149. clearTimeout(ht);
  150. });
  151.  
  152. })();