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.

// ==UserScript==
// @name         Change YouTube Leftbar Subscription Links To Channel/User Video Page
// @namespace    ChangeYouTubeLeftbarSubscriptionLinksToChannelUserVideoPage
// @version      1.1.16
// @license      AGPL v3
// @author       jcunews
// @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.
// @match        *://www.youtube.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(yigd => {

  //===== Configuration Start ===

  var changeLinksURL = true;
  var moveLinksToTop = true;

  //===== Configuration End ===

  function changeUrl(url, m) {
    if (m = url.match(/^\/feed\/subscriptions\/([^\/]+)$/)) {
      return "/channel/" + m[1] + "/videos";
    } else return url + "/videos";
  }

  function updateEndpoint(ep) {
    ep.commandMetadata.webCommandMetadata.url = changeUrl(ep.commandMetadata.webCommandMetadata.url);
    if (ep.webNavigationEndpointData) ep.webNavigationEndpointData.url = changeUrl(ep.webNavigationEndpointData.url);
    (function waitGD(gd) {
      if ((gd = window["guide-renderer"].__data) && (gd = gd.data) && (gd = gd.items) && (gd = gd.reduce(
        (r, v) => {
          if (!r && (v = v.guideSubscriptionsSectionRenderer) && (v = v.items)) {
            r = v.reduce((r, e) => {
              if (e.guideCollapsibleEntryRenderer) {
                e.guideCollapsibleEntryRenderer.expandableItems.forEach(t => {
                  if (!t.guideEntryRenderer.icon || (t.guideEntryRenderer.icon.iconType !== "ADD_CIRCLE")) r.push(t.guideEntryRenderer.navigationEndpoint)
                });
              } else r.push(e.guideEntryRenderer.navigationEndpoint);
              return r;
            }, []);
          }
          return r;
        }, null
      ))) {
        fetch(location.protocol + "//" + location.host + ep.commandMetadata.webCommandMetadata.url, {credentials: "omit"}).then(r => r.text().then((h, o) => {
          if ((h = h.match(/window\["ytInitialData"\] = (\{.*?)\};\n/)) && (h = JSON.parse(h[1] + "}").contents.twoColumnBrowseResultsRenderer.tabs)) {
            h.some(a => {
              if ((/^\/[^\/]+\/[^\/]+\/videos$/).test((a = a.tabRenderer.endpoint).commandMetadata.webCommandMetadata.url)) {
                ep.browseEndpoint.params = a.browseEndpoint.params;
                ep.commandMetadata.webCommandMetadata.url = a.commandMetadata.webCommandMetadata.url;
                if (ep.webNavigationEndpointData) ep.webNavigationEndpointData.url = a.commandMetadata.webCommandMetadata.url;
                gd.some(d => {
                  if (d.browseEndpoint.browseId === a.browseEndpoint.browseId) {
                    d.browseEndpoint.params = a.browseEndpoint.params;
                    d.commandMetadata.webCommandMetadata.url = a.commandMetadata.webCommandMetadata.url;
                    if (d.webNavigationEndpointData) d.webNavigationEndpointData.url = a.commandMetadata.webCommandMetadata.url;
                    return true;
                  }
                });
                return true;
              }
            })
          }
        }))
      } else setTimeout(waitGD, 100)
    })()
  }

  function patchGuide(guide, z) {
    if (guide && guide.items && !guide.cysl_done) try {
      guide.cysl_done = 1;
      guide.items.forEach((v, vc, l, w, c, i) => {
        if (v.guideSubscriptionsSectionRenderer) {
          //change links' URL
          if (changeLinksURL) {
            v.guideSubscriptionsSectionRenderer.items.forEach(w => {
              if (w.guideCollapsibleEntryRenderer) {
                w.guideCollapsibleEntryRenderer.expandableItems.forEach(x => {
                  if (x.guideEntryRenderer.badges && (x = x.guideEntryRenderer.navigationEndpoint)) updateEndpoint(x);
                });
              } else if (w = w.guideEntryRenderer.navigationEndpoint) updateEndpoint(w);
            });
          }
          //move links with new uploads to top
          if (moveLinksToTop) {
            v = v.guideSubscriptionsSectionRenderer.items;
            vc = v.length - 1;
            w = v[vc].guideCollapsibleEntryRenderer;
            l = v.splice(0, vc); //main links to list1
            c = -1;
            if (w) { //collapsed links to list1
              (w = w.expandableItems).some((e, i) => {
                if (!e.guideEntryRenderer.badges) {
                  c = i;
                  return true;
                }
              });
              l.push.apply(l, w.splice(0, c));
            }
            c = []; //new links to main links. c = collapsed new links in list2
            for (i = l.length - 1; i >= 0; i--) {
              if (l[i].guideEntryRenderer.count || (l[i].guideEntryRenderer.presentationStyle === "GUIDE_ENTRY_PRESENTATION_STYLE_NEW_CONTENT")) {
                if (vc--) {
                  v.unshift(l.splice(i, 1)[0]);
                } else c.unshift(l.splice(i, 1)[0]);
              }
            }
            c.push.apply(c, l); //list1 to list2
            if (vc) { //fill up main links from list2
              l = c.splice(0, vc);
              l.unshift.apply(l, [v.length - 1, 0]);
              v.splice.apply(v, l);
            }
            if (w) w.unshift.apply(w, c);
          }
        }
      });
      return true;
    } catch(z) {}
    return false;
  }

  Object.defineProperty(window, "ytInitialGuideData", {
    get(v) {
      return yigd;
    },
    set(v) {
      delete window.ytInitialGuideData;
      patchGuide(v);
      return yigd = v;
    }
  });

  JSON.parse_cylslcvp = JSON.parse;
  JSON.parse = function(txt) {
    var res = JSON.parse_cylslcvp.apply(this, arguments);
    if ((/\/youtubei\/v1\/guide\?/).test(JSON.url_cylslcvp)) patchGuide(res);
    return res;
  };

  var fetch_ = window.fetch;
  window.fetch = function(opts) {
    var fres = fetch_.apply(this, arguments), fthen = fres.then;
    if ((/\/youtubei\/v1\/guide\?/).test(opts.url)) {
      JSON.url_cylslcvp = opts.url;
      fres.then = function(fn) {
        var fthenfn = fn;
        fn = function(fresp) {
          var frjson = fresp.json;
          fresp.json = function() {
            var jres = frjson.apply(this, arguments), jthen = jres.then;
            jres.then = function(jfn) {
              var jthenfn = jfn;
              jfn = function(jresp) {
                patchGuide(jresp);
                console.log(jresp);
                return jthenfn.apply(this, arguments);
              };
              return jthen.apply(this, arguments);
            };
            return jres;
          }
          return fthenfn.apply(this, arguments);
        };
        return fthen.apply(this, arguments);
      };
    } else JSON.url_cylslcvp = "";
    return fres;
  };
  
  var ht = 0;
  (function chkSpf() {
    clearTimeout(ht);
    if (window.spf && spf.request && !spf.request_cysl) {
      spf.request_cysl = spf.request;
      spf.request = function(a, b) {
        if (b && b.onDone) {
          var onDone_ = b.onDone;
          b.onDone = function(response) {
            if (response && (/\/guide_ajax\?/).test(response.url) && response.response && response.response.response) {
              patchGuide(response.response.response);
            }
            return onDone_.apply(this, arguments);
          };
          return this.request_cysl.apply(this, arguments);
        }
      };
      return;
    }
    ht = setTimeout(chkSpf, 0);
  })();
  addEventListener("click", (ev, a) => {
    if ((a = ev.target) && a.matches && a.matches("ytd-guide-section-renderer:nth-child(2) ytd-guide-entry-renderer *")) {
      while (a.tagName !== "A") a = a.parentNode;
      if (/^\/(c|channel|u|user)\//.test(a.pathname)) {
        a = a.__dataHost.__data.data.navigationEndpoint;
        ev.stopImmediatePropagation();
        ev.preventDefault();
        a.browseEndpoint.params = encodeURIComponent(btoa("\x12\x06videos"));
        a.browseEndpoint.canonicalBaseUrl = a.commandMetadata.webCommandMetadata.url.substring(0, a.commandMetadata.webCommandMetadata.url.lastIndexOf("/"));
        nav.navigate(a, false, undefined, {});
      }
    }
  }, true);
  addEventListener("load", () => {
    clearTimeout(ht);
  });

})();