YouTube Uploads Sorter Button

Adds a button to a YouTube channel's videos page which sorts recent uploads by views

  1. // ==UserScript==
  2. // @name YouTube Uploads Sorter Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.16.0
  5. // @description Adds a button to a YouTube channel's videos page which sorts recent uploads by views
  6. // @author Lex
  7. // @match *://*.youtube.com/@*
  8. // @match *://*.youtube.com/*/featured
  9. // @match *://*.youtube.com/*/videos
  10. // @exclude-match *://*.youtube.com/watch
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function($) {
  15. 'use strict';
  16.  
  17. function addButton() {
  18. if (!document.getElementById("sortViewButton")) {
  19. const chip = document.createElement("button")
  20. chip.id = "sortViewButton"
  21. chip.addEventListener("click", sortByViews)
  22. Object.assign(chip.style, {border: "none", borderRadius: "8px", padding: "8px 15px", marginLeft: "1em", cursor: "pointer"})
  23. const container = document.querySelector("#chips-wrapper iron-selector")
  24. container.append(chip)
  25. chip.textContent = "Sort by Views"
  26. }
  27. }
  28.  
  29. function parseViewNumber(str) {
  30. /* Parses 405K, 1.5M etc */
  31. let multiplier = 1;
  32. if (str.endsWith("K")) {
  33. multiplier = 1000;
  34. } else if (str.endsWith("M")) {
  35. multiplier = 1000000;
  36. }
  37. return parseFloat(str) * multiplier;
  38. }
  39.  
  40. function getViewsShort(e) {
  41. // viewsText = "405K views"
  42. const viewsText = e.querySelector(".inline-metadata-item").textContent;
  43. const viewsStr = viewsText.split(" ")[0];
  44. return parseViewNumber(viewsStr);
  45. }
  46.  
  47. function getViewsVideo(e) {
  48. const viewsTitle = e.querySelector('a#video-title-link').getAttribute("aria-label");
  49. //console.log(`Found title: ${viewsTitle}`);
  50. if (viewsTitle.search(/No views$/) > -1) // video has no views yet
  51. return 0;
  52. else {
  53. const views = parseInt(/([\d,]+) views( - play Short)?$/.exec(viewsTitle)[1].replace(/,/g, ""));
  54. return views;
  55. }
  56. }
  57.  
  58. function getViews(e) {
  59. // Try to get the views of a regular video, and if that fails try to get views as a Short
  60. try {
  61. return getViewsVideo(e);
  62. } catch(err) {
  63. try {
  64. return getViewsShort(e);
  65. } catch(err) {
  66. return 0;
  67. }
  68. }
  69. }
  70.  
  71. function sortByViews() {
  72. console.log("Sorting...");
  73. const items = document.querySelectorAll("ytd-rich-item-renderer");
  74. console.log(`Found ${items.length} videos on the page.`);
  75. //console.log(items);
  76. //console.log(getViews(items[0]));
  77.  
  78. // Array of each parent for a given index.
  79. // e.g. if there are 4 videos in the first row container, the first 4 indexes are that first row
  80. const parents = [...items].map(e => e.parentNode);
  81. //console.log(parents);
  82. const sorted = [...items].sort(function(a, b) {
  83. return getViews(b) - getViews(a);
  84. });
  85. const infiniteScrollItem = document.getElementsByTagName("ytd-continuation-item-renderer")[0]
  86. if (infiniteScrollItem)
  87. infiniteScrollItem.parentNode.removeChild(infiniteScrollItem)
  88. for (let item of sorted) {
  89. // Remove item from its parent and append it to the ordered parent
  90. const parent = parents.shift();
  91. //console.log("Parent: ", parent);
  92. //console.log("Removing", item, "from its parent");
  93. item.parentNode.removeChild(item);
  94. parent.append(item);
  95. }
  96. if (infiniteScrollItem)
  97. sorted[sorted.length - 1].parentNode.append(infiniteScrollItem)
  98. }
  99.  
  100. function waitForLoad(query, callback) {
  101. if (document.querySelector(query)) {
  102. callback();
  103. } else {
  104. setTimeout(waitForLoad.bind(null, query, callback), 100);
  105. }
  106. }
  107.  
  108. waitForLoad("#chips-wrapper", addButton);
  109. })();