Export Youtube Playlist in tab delimited text

Creates the current playlist as tab delimited text to be easily copied

目前为 2024-08-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Export Youtube Playlist in tab delimited text
  3. // @description Creates the current playlist as tab delimited text to be easily copied
  4. // @author 1N07 & MK
  5. // @namespace max44
  6. // @homepage https://greasyfork.org/en/users/309172-max44
  7. // @match *://*.youtube.com/*
  8. // @match *://*.youtu.be/*
  9. // @icon https://cdn.icon-icons.com/icons2/1488/PNG/512/5295-youtube-i_102568.png
  10. // @version 2.0
  11. // @license MIT
  12. // @run-at document-idle
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  17. 'use strict';
  18.  
  19. //This document requires TrustedHTML assignment https://greasyfork.org/en/discussions/development/220765-this-document-requires-trustedhtml-assignment
  20. if (window.trustedTypes != undefined && window.trustedTypes.createPolicy) {
  21. window.trustedTypes.createPolicy('default', {
  22. createHTML: (string, sink) => string
  23. });
  24. }
  25.  
  26. var listCreationAllowed = true;
  27. var urlAtLastCheck = "";
  28. var htmlText;
  29. setInterval(function() {
  30. if (urlAtLastCheck != window.location.href) {
  31. urlAtLastCheck = window.location.href;
  32. if (urlAtLastCheck.includes("/playlist?list=")) InsertButtonASAP();
  33. }
  34. }, 100);
  35.  
  36. function InsertButtonASAP() {
  37. var qRes = document.querySelectorAll("#exportTabTextList"); //Remove previous button
  38. for (let i = 0; i < qRes.length; i++) {
  39. qRes[i].remove();
  40. }
  41.  
  42. let buttonInsertInterval = setInterval(function() {
  43. if (document.querySelectorAll("#exportTabTextList").length == 0) {
  44.  
  45. if (document.querySelectorAll("div.ytd-playlist-header-renderer > div.play-menu.ytd-playlist-header-renderer").length > 0) { //New design
  46. qRes = document.querySelectorAll("div.metadata-wrapper.ytd-playlist-header-renderer > div.play-menu.ytd-playlist-header-renderer");
  47. if (qRes != null && qRes.length > 0) {
  48. htmlText = "<button id='exportTabTextList' class='yt-spec-button-shape-next--size-m' style='font-family: Roboto, Arial, sans-serif; font-size: 13px; margin-bottom: 16px; padding-top:2px; border: none; height: 28px; line-height: normal; opacity: 0.8;'>Export list as tab delimited text</button>";
  49. if (window.trustedTypes != undefined) qRes[0].insertAdjacentHTML("afterend", window.trustedTypes.defaultPolicy.createHTML(htmlText));
  50. else qRes[0].insertAdjacentHTML("afterend", htmlText);
  51. }
  52. qRes = document.querySelectorAll("#exportTabTextList");
  53. if (qRes != null && qRes.length > 0) {
  54. qRes[0].onclick = ScrollUntilFullListVisible;
  55. }
  56.  
  57. } else if (document.querySelectorAll("ytd-playlist-sidebar-renderer:not([hidden]) > ytd-playlist-sidebar-primary-info-renderer.style-scope.ytd-playlist-sidebar-renderer").length > 0) { //Old design
  58. qRes = document.querySelectorAll("ytd-playlist-sidebar-primary-info-renderer.style-scope.ytd-playlist-sidebar-renderer");
  59. if (qRes != null && qRes.length > 0) {
  60. htmlText = "<button id='exportTabTextList' style='font-family: Roboto, Arial, sans-serif; font-size: 13px; margin: 10px 0px;'>Export list as tab delimited text</button>"
  61. if (window.trustedTypes != undefined) qRes[0].insertAdjacentHTML("afterend", window.trustedTypes.defaultPolicy.createHTML(htmlText));
  62. else qRes[0].insertAdjacentHTML("afterend", htmlText);
  63. }
  64. qRes = document.querySelectorAll("#exportTabTextList");
  65. if (qRes != null && qRes.length > 0) {
  66. qRes[0].onclick = ScrollUntilFullListVisible;
  67. }
  68. }
  69.  
  70. //Check whether unavailable videos are hidden or not
  71. //var i;
  72. //var strAux = "";
  73. //var flgHidden = false;
  74. //var myNodeList = document.querySelectorAll("#text");
  75. //for (i = 0; i < myNodeList.length; i++) {
  76. // if (myNodeList[i].className.indexOf("style-scope ytd-alert-with-button-renderer") > -1) {
  77. // strAux = myNodeList[i].innerText;
  78. // strAux = strAux.trim();
  79. // strAux = strAux.toLowerCase();
  80. // if (strAux.indexOf("unavailable videos are hidden") > -1) {
  81. // flgHidden = true;
  82. // break;
  83. // }
  84. // }
  85. //}
  86. //if (flgHidden) {
  87. // $("#exportTabTextList").click(ScrollAsPossible); //Unavailable videos are hidden
  88. //} else {
  89. //$("#exportTabTextList").click(ScrollUntilFullListVisible);
  90. //}
  91. //clearInterval(buttonInsertInterval); - Do not clear interval in order to add button back if playlist is rebuilt
  92. }
  93. }, 100);
  94. }
  95.  
  96. function ScrollUntilFullListVisible() {
  97. if (!listCreationAllowed) return;
  98.  
  99. var numOfVideos = document.querySelectorAll(".metadata-stats");
  100. if (numOfVideos != null && numOfVideos.length > 0) {
  101. numOfVideos = numOfVideos[0].innerText.replace(/\ .*/g, '');
  102.  
  103. listCreationAllowed = false;
  104. var qRes = document.querySelectorAll("#exportTabTextList:not(.yt-spec-button-shape-next--size-m)");
  105. if (qRes != null && qRes.length > 0) {
  106. htmlText = `<p id="listBuildMessage" style="color: red; font-size: 1.33em;">Getting full list, please wait...</p>`;
  107. if (window.trustedTypes != undefined) qRes[0].insertAdjacentHTML("afterend", window.trustedTypes.defaultPolicy.createHTML(htmlText));
  108. else qRes[0].insertAdjacentHTML("afterend", htmlText);
  109. }
  110. qRes = document.querySelectorAll("#exportTabTextList.yt-spec-button-shape-next--size-m");
  111. if (qRes != null && qRes.length > 0) {
  112. htmlText = `<p id="listBuildMessage" style="color: red; font-size: 1.33em; margin-bottom: 16px; mix-blend-mode: lighten;">Getting full list, please wait...</p>`;
  113. if (window.trustedTypes != undefined) qRes[0].insertAdjacentHTML("afterend", window.trustedTypes.defaultPolicy.createHTML(htmlText));
  114. else qRes[0].insertAdjacentHTML("afterend", htmlText);
  115. }
  116.  
  117. let scrollInterval = setInterval(function(){
  118. if (document.querySelectorAll("ytd-continuation-item-renderer.ytd-playlist-video-list-renderer").length > 0) {
  119. $(document).scrollTop($(document).height());
  120. } else {
  121. BuildAndDisplayList();
  122. clearInterval(scrollInterval);
  123. }
  124. }, 100);
  125. }
  126. }
  127.  
  128. /*function ScrollAsPossible() { //If unavailable videos are hidden
  129. if (!listCreationAllowed) return;
  130.  
  131. listCreationAllowed = false;
  132. $("#exportTabTextList").after(`<p id="listBuildMessage" style="color: red; font-size: 1.33em;">Getting full list, please wait...</p>`);
  133. $(document).scrollTop($(document).height());
  134. let scrollInterval2 = setInterval(function(){
  135. if (CheckSpinner()) {
  136. $(document).scrollTop($(document).height());
  137. } else {
  138. BuildAndDisplayList();
  139. clearInterval(scrollInterval2);
  140. }
  141. }, 500);
  142. }
  143.  
  144. function CheckSpinner() { //True if playlist is still loading
  145. var i;
  146. var myNodeList = document.querySelectorAll("#spinner");
  147. for (i = 0; i < myNodeList.length; i++) {
  148. if (myNodeList[i].className.indexOf("style-scope ytd-continuation-item-renderer") > -1) return true;
  149. }
  150. return false;
  151. }*/
  152.  
  153. function BuildAndDisplayList() {
  154.  
  155. let list = "<Name>\t<Channel>\t<Duration>\t<URL>";
  156. var myNodeList = document.querySelectorAll("ytd-playlist-video-renderer");
  157. var i;
  158. var myCount = 0;
  159. for (i = 0; i < myNodeList.length; i++) {
  160. if (myNodeList[i].className.indexOf("style-scope ytd-playlist-video-list-renderer") > -1) {
  161. var mySpanList = myNodeList[i].querySelectorAll("span");
  162. var myAList = myNodeList[i].querySelectorAll("a");
  163. var j;
  164. var strAux = "";
  165. var strAux2 = "";
  166. myCount++;
  167. for (j = 0; j < myAList.length; j++) {
  168. if (myAList[j].id == "video-title") {
  169. strAux = myAList[j].innerText; //Video title
  170. strAux = strAux.replace(/[\x0D\x0A]/g, " ");
  171. list += "\n" + strAux.trim();
  172. strAux2 = myAList[j].href; //Video URL
  173. strAux2 = strAux2.replace(/&list=.*&index=\d+/gi, ""); //Remove reference to list and video's index
  174. strAux2 = strAux2.replace(/&t=.*$/gi, ""); //Remove timestamp
  175. strAux2 = strAux2.replace(/&pp=.*$/gi, ""); //Remove pp parameter
  176. }
  177. }
  178. list += "\t";
  179. for (j = 0; j < myAList.length; j++) {
  180. if (myAList[j].className == "yt-simple-endpoint style-scope yt-formatted-string") {
  181. strAux = myAList[j].innerText; //Channel name
  182. strAux = strAux.replace(/[\x0D\x0A]/g, " ");
  183. list += strAux.trim();
  184. }
  185. }
  186. list += "\t ";
  187. for (j = 0; j < mySpanList.length; j++) {
  188. if (mySpanList[j].className == "style-scope ytd-thumbnail-overlay-time-status-renderer") {
  189. strAux = mySpanList[j].innerText; //Duration
  190. strAux = strAux.replace(/[\x0D\x0A]/g, " ");
  191. list += strAux.trim();
  192. }
  193. }
  194. list += "\t" + strAux2.trim(); //Video URL is the last column
  195. }
  196. }
  197.  
  198. var qRes = document.querySelectorAll("body");
  199. if (qRes != null && qRes.length > 0) {
  200. htmlText = '<div id="tablistDisplayContainer" style="position: fixed; z-index: 9999; top: 5%; right: 5%; background-color: gray; padding: 10px; border-radius: 5px;"><button id="selectAllAndCopy" style="font-family: Roboto, Arial, sans-serif; font-size: 13px;">Select all and copy</button>&nbsp;&nbsp;&nbsp;<button id="closeTheListThing" style="font-family: Roboto, Arial, sans-serif; font-size: 13px;">Close</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-family: Roboto, Arial, sans-serif; font-size: 13px; font-weight: bold; color: white">Total videos in list: '+myCount+'</span><br><br><textarea id="tabPlayList" style="width: 50vw; height: 80vh; max-width: 90vw; max-height: 90vh;">'+list+'</textarea></div>';
  201. if (window.trustedTypes != undefined) qRes[0].insertAdjacentHTML("afterend", window.trustedTypes.defaultPolicy.createHTML(htmlText));
  202. else qRes[0].insertAdjacentHTML("afterend", htmlText);
  203. }
  204.  
  205. qRes = document.querySelector("#listBuildMessage");
  206. if (qRes != null) qRes.remove();
  207.  
  208. qRes = document.querySelector("#closeTheListThing");
  209. if (qRes != null) {
  210. qRes.onclick = function() {
  211. qRes = document.querySelector("#tablistDisplayContainer");
  212. if (qRes != null) qRes.remove();
  213. listCreationAllowed = true;
  214. }
  215. }
  216.  
  217. qRes = document.querySelector("#selectAllAndCopy");
  218. if (qRes != null) {
  219. qRes.onclick = function() {
  220. document.getElementById("tabPlayList").select();
  221. document.execCommand("copy");
  222. }
  223. }
  224. }
  225.  
  226. }) ();