Greasy Fork 支持简体中文。

Youtube Subscription List Quick Playlist

Quickly create playlists from your Youtube subscription feed

  1. // ==UserScript==
  2. // @name Youtube Subscription List Quick Playlist
  3. // @namespace https://greasyfork.org/en/users/13981-chk1
  4. // @description Quickly create playlists from your Youtube subscription feed
  5. // @include https://www.youtube.com/feed/subscriptions*
  6. // @version 1.0.3
  7. // @grant GM_addStyle
  8. // @run-at document-end
  9. // ==/UserScript==
  10.  
  11. GM_addStyle("#subs2playlistContainer { position:fixed; bottom: 10px; right: 10px; width: 180px; } ");
  12. GM_addStyle("#subs2playlistLink { display: block; clear: both; } ");
  13. GM_addStyle("#subs2playlistClear { float: right; font-size: 12px; padding-top: 5px; padding-bottom: 5px; display: block; color: #bb0000; }");
  14. GM_addStyle("#subs2playlistCopy { float: left; font-size: 12px; padding-top: 5px; padding-bottom: 5px; display: block; color: #000; }");
  15. GM_addStyle("#subs2playlistCopyTemp { width:1px; height:1px; position:fixed; }");
  16. GM_addStyle("#subs2playlistContainer input { font-size: 12px; display:block; color: #767676; clear:both; width: 100%; border: 1px solid #d5d5d5; border-radius: 2px; font-family: monospace }");
  17. GM_addStyle("#subs2playlistContainer span { font-size: 12px; display:block; clear:both; transition: color 0.2s; color: #767676; }");
  18. GM_addStyle("#subs2playlistContainer span.warning { color: #bb0000; }");
  19. GM_addStyle(".subs2playlist.subs2playlist-add span::after { content: 'Add to playlist' }");
  20. GM_addStyle(".subs2playlist.subs2playlist-remove span::after { content: 'Remove from playlist' }");
  21.  
  22. var videoIds = [];
  23. var playlistElements;
  24.  
  25. // create DOM nodes
  26. function createPlaylistContainer() {
  27. var playlistLinkContainer = document.createElement('div');
  28. playlistLinkContainer.className = "yt-card yt-card-has-padding";
  29. playlistLinkContainer.id = "subs2playlistContainer";
  30.  
  31. var playlistLinkH3 = document.createElement('h3');
  32. playlistLinkH3.className = "yt-lockup-title";
  33.  
  34. var playlistLink = document.createElement('a');
  35. playlistLink.href = "https://www.youtube.com/watch_videos?video_ids=";
  36. playlistLink.id = "subs2playlistLink";
  37.  
  38. var playlistLinkText = document.createTextNode("Your playlist link");
  39. var playlistLinkCount = document.createElement("span");
  40. playlistLinkCount.innerHTML = "0 videos";
  41. var clearLink = document.createElement("a");
  42. clearLink.id = "subs2playlistClear";
  43. clearLink.onclick = function() { clearPlaylist() };
  44.  
  45. var clearText = document.createTextNode("Clear");
  46. var copyLink = document.createElement("a");
  47. copyLink.id = "subs2playlistCopy";
  48.  
  49. var copyText = document.createTextNode('Copy IDs');
  50. var copyLinkInput = document.createElement('input');
  51. copyLinkInput.type = "text";
  52. copyLinkInput.placeholder = "video IDs...";
  53. copyLinkInput.className = "yt-uix-form-input-bidi";
  54. copyLink.onclick = function() {
  55. html5Copy(copyLinkInput);
  56. };
  57.  
  58. var playlistLinkFunctionContainer = document.createElement('div');
  59. var introText = document.createElement("span");
  60. introText.innerHTML = "Add videos from your subscription box to create a quick playlist.";
  61.  
  62. copyLink.appendChild(copyText);
  63. clearLink.appendChild(clearText);
  64. playlistLink.appendChild(playlistLinkText);
  65. playlistLinkH3.appendChild(playlistLink);
  66. playlistLinkContainer.appendChild(playlistLinkH3);
  67.  
  68. playlistLinkFunctionContainer.appendChild(playlistLinkCount);
  69. playlistLinkFunctionContainer.appendChild(clearLink);
  70. playlistLinkFunctionContainer.appendChild(copyLink);
  71. playlistLinkFunctionContainer.appendChild(copyLinkInput);
  72. playlistLinkFunctionContainer.style.display = "none";
  73. playlistLinkContainer.appendChild(playlistLinkH3);
  74. playlistLinkContainer.appendChild(introText);
  75. playlistLinkContainer.appendChild(playlistLinkFunctionContainer);
  76.  
  77. return {
  78. 'container': playlistLinkContainer,
  79. 'link': playlistLink,
  80. 'text': playlistLinkText,
  81. 'plain': copyLinkInput,
  82. 'count': playlistLinkCount,
  83. 'intro': introText,
  84. 'functioncontainer': playlistLinkFunctionContainer
  85. };
  86. }
  87.  
  88. // copy video IDs to clipboard
  89. function html5Copy(inputnode){
  90. var node = document.createElement('pre');
  91. node.className = 'subs2playlistCopyTemp';
  92. node.textContent = inputnode.value;
  93. document.body.appendChild(node);
  94.  
  95. var selection = getSelection();
  96. selection.removeAllRanges()
  97. var range = document.createRange();
  98. range.selectNodeContents(node);
  99. console.log(range);
  100. selection.addRange(range);
  101. document.execCommand('copy');
  102.  
  103. document.body.removeChild(node);
  104. }
  105.  
  106. // update playlist link, run after adding/removing videos
  107. function updatePlaylistLink() {
  108. // toggle intro text visibility off
  109. playlistElements.functioncontainer.style.display = "block";
  110. playlistElements.intro.style.display = "none";
  111.  
  112. // check playlist length for known limits
  113. if(videoIds.length > 20) {
  114. playlistElements.count.classList.add("warning");
  115. playlistElements.count.textContent = ""+videoIds.length+" videos - The playlist link will only play the first 20 videos.";
  116. } else if(playlistElements.text.textContent.length > 2000){
  117. playlistElements.count.classList.add("warning");
  118. playlistElements.count.textContent = ""+videoIds.length+" videos - Too many videos, URL is too long. Some videos in the playlist link may not work.";
  119. } else {
  120. playlistElements.count.classList.remove("warning");
  121. playlistElements.link.href="https://www.youtube.com/watch_videos?video_ids="+videoIds.join(',');
  122. playlistElements.plain.value=videoIds.join(',');
  123. playlistElements.count.textContent = ""+videoIds.length+" videos";
  124. }
  125. }
  126.  
  127. // clear all video IDs, reset buttons
  128. function clearPlaylist() {
  129. videoIds = [];
  130. var buttons = document.querySelectorAll('#browse-items-primary > .section-list .subs2playlist');
  131. for (var i = 0; i < buttons.length; ++i) {
  132. buttons[i].classList.remove("subs2playlist-remove");
  133. //buttons[i].classList.remove("yt-uix-button-subscribed-branded");
  134. buttons[i].classList.remove("c4-module-editor-delete");
  135. buttons[i].classList.add("subs2playlist-add");
  136. buttons[i].classList.add("c4-editor-plus");
  137. }
  138. updatePlaylistLink();
  139. }
  140.  
  141. // only add videos that aren't duplicates
  142. function toggleVideoId(videoId){
  143. var alreadyIn = videoIds.indexOf(videoId);
  144. if(alreadyIn === -1){
  145. videoIds.push(videoId);
  146. return true;
  147. } else {
  148. videoIds.splice(alreadyIn, 1);
  149. return false;
  150. }
  151. }
  152.  
  153. // add a button to a subscription list item
  154. function createPlusButton(videoId){
  155. var container = document.createElement('button');
  156. container.setAttribute('onclick', 'toggleVideoId(\''+videoId+'\');');
  157. container.className = "subs2playlist subs2playlist-add yt-uix-button yt-uix-button-size-default yt-uix-button-default yt-uix-button-has-icon no-icon-markup yt-uix-inlineedit-edit c4-editor-plus";
  158. container.onclick = function(){
  159. toggleVideoId(videoId);
  160. container.classList.toggle("c4-module-editor-delete");
  161. container.classList.toggle("c4-editor-plus");
  162. container.classList.toggle("subs2playlist-remove");
  163. container.classList.toggle("subs2playlist-add");
  164. updatePlaylistLink();
  165. };
  166. container.innerHTML = "<span class=\"yt-uix-button-content\"></span>";
  167. return container;
  168. }
  169.  
  170. // iterate subscription list items, then add buttons
  171. function appendAllTheThings(node) {
  172. var videoId = node.getAttribute("data-context-item-id");
  173. var plusButtonNode = createPlusButton(videoId);
  174. node.appendChild(plusButtonNode);
  175. }
  176.  
  177. var observerConfig = {
  178. childList: true,
  179. attributes: true,
  180. subtree: false,
  181. attributeOldValue: false
  182. };
  183.  
  184. // add buttons to items loaded after clicking "load more" or endless scroll, which are dynamically added
  185. var listObserver = new MutationObserver(function(mutations) {
  186. mutations.forEach(function(mutation) {
  187. if(mutation.type == "childList" && mutation.addedNodes.length >= 1) {
  188. console.log(mutation);
  189. for (var i = 0; i < mutation.addedNodes.length; ++i) {
  190. var node = mutation.addedNodes[i];
  191. if(node.nodeType === 1){
  192. var videoLinkContainer = node.querySelector('.yt-lockup');
  193. appendAllTheThings(videoLinkContainer);
  194. }
  195. }
  196. }
  197. });
  198. });
  199. var subListContainer = document.querySelector('#browse-items-primary > .section-list');
  200. listObserver.observe(subListContainer, observerConfig);
  201.  
  202. // first run: create our container, add buttons to playlist items
  203. function firstRun(){
  204. var videoLinkNodes = document.querySelectorAll('div.yt-lockup');
  205. for (var i = 0; i < videoLinkNodes.length; ++i) {
  206. var node = videoLinkNodes[i];
  207. appendAllTheThings(node);
  208. }
  209. playlistElements = createPlaylistContainer();
  210. document.body.appendChild(playlistElements.container);
  211. }
  212.  
  213. firstRun();