Youtube Subscription List Quick Playlist

Quickly create playlists from your Youtube subscription feed

// ==UserScript==
// @name        Youtube Subscription List Quick Playlist
// @namespace   https://gf.qytechs.cn/en/users/13981-chk1
// @description Quickly create playlists from your Youtube subscription feed
// @include     https://www.youtube.com/feed/subscriptions*
// @version     1.0.3
// @grant       GM_addStyle
// @run-at      document-end
// ==/UserScript==

GM_addStyle("#subs2playlistContainer { position:fixed; bottom: 10px; right: 10px; width: 180px; } ");
GM_addStyle("#subs2playlistLink { display: block; clear: both; } ");
GM_addStyle("#subs2playlistClear { float: right; font-size: 12px; padding-top: 5px; padding-bottom: 5px; display: block; color: #bb0000; }");
GM_addStyle("#subs2playlistCopy { float: left; font-size: 12px; padding-top: 5px; padding-bottom: 5px; display: block; color: #000; }");
GM_addStyle("#subs2playlistCopyTemp { width:1px; height:1px; position:fixed; }");
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 }");
GM_addStyle("#subs2playlistContainer span { font-size: 12px; display:block; clear:both; transition: color 0.2s; color: #767676; }");
GM_addStyle("#subs2playlistContainer span.warning { color: #bb0000; }");
GM_addStyle(".subs2playlist.subs2playlist-add span::after { content: 'Add to playlist' }");
GM_addStyle(".subs2playlist.subs2playlist-remove span::after { content: 'Remove from playlist' }");

var videoIds = [];
var playlistElements;

// create DOM nodes
function createPlaylistContainer() {
	var playlistLinkContainer       = document.createElement('div'); 
	playlistLinkContainer.className = "yt-card yt-card-has-padding";
	playlistLinkContainer.id        = "subs2playlistContainer";

	var playlistLinkH3              = document.createElement('h3'); 
	playlistLinkH3.className        = "yt-lockup-title";

	var playlistLink                = document.createElement('a'); 
	playlistLink.href               = "https://www.youtube.com/watch_videos?video_ids=";
	playlistLink.id                 = "subs2playlistLink";

	var playlistLinkText            = document.createTextNode("Your playlist link"); 
	var playlistLinkCount           = document.createElement("span"); 
	playlistLinkCount.innerHTML     = "0 videos"; 
	
	var clearLink                   = document.createElement("a"); 
	clearLink.id                    = "subs2playlistClear";
	clearLink.onclick               = function() { clearPlaylist() };

	var clearText                   = document.createTextNode("Clear"); 
	var copyLink                    = document.createElement("a"); 
	copyLink.id                     = "subs2playlistCopy";

	var copyText = document.createTextNode('Copy IDs'); 
	var copyLinkInput = document.createElement('input'); 
	copyLinkInput.type = "text"; 
	copyLinkInput.placeholder = "video IDs..."; 
	copyLinkInput.className = "yt-uix-form-input-bidi"; 
	copyLink.onclick = function() { 
		html5Copy(copyLinkInput);
	};

	var playlistLinkFunctionContainer = document.createElement('div'); 
	var introText                   = document.createElement("span"); 
	introText.innerHTML             = "Add videos from your subscription box to create a quick playlist."; 

	copyLink.appendChild(copyText);
	clearLink.appendChild(clearText);
	playlistLink.appendChild(playlistLinkText);
	playlistLinkH3.appendChild(playlistLink);
	playlistLinkContainer.appendChild(playlistLinkH3);

	playlistLinkFunctionContainer.appendChild(playlistLinkCount);
	playlistLinkFunctionContainer.appendChild(clearLink);
	playlistLinkFunctionContainer.appendChild(copyLink);
	playlistLinkFunctionContainer.appendChild(copyLinkInput);
	playlistLinkFunctionContainer.style.display = "none";
	
	playlistLinkContainer.appendChild(playlistLinkH3);
	playlistLinkContainer.appendChild(introText);
	playlistLinkContainer.appendChild(playlistLinkFunctionContainer);

	return {
		'container': playlistLinkContainer, 
		'link': playlistLink, 
		'text': playlistLinkText,
		'plain': copyLinkInput,
		'count': playlistLinkCount,
		'intro': introText,
		'functioncontainer': playlistLinkFunctionContainer
	};
}

// copy video IDs to clipboard
function html5Copy(inputnode){
	var node = document.createElement('pre');
	node.className = 'subs2playlistCopyTemp';
    node.textContent = inputnode.value;
	document.body.appendChild(node);

	var selection = getSelection();
	selection.removeAllRanges()
	var range = document.createRange();
	range.selectNodeContents(node);
	console.log(range);
	selection.addRange(range);
	document.execCommand('copy');

	document.body.removeChild(node);
}

// update playlist link, run after adding/removing videos
function updatePlaylistLink() {
	// toggle intro text visibility off
	playlistElements.functioncontainer.style.display = "block";
	playlistElements.intro.style.display = "none";

	// check playlist length for known limits
	if(videoIds.length > 20) {
		playlistElements.count.classList.add("warning");
		playlistElements.count.textContent = ""+videoIds.length+" videos - The playlist link will only play the first 20 videos.";
	} else if(playlistElements.text.textContent.length > 2000){
		playlistElements.count.classList.add("warning");
		playlistElements.count.textContent = ""+videoIds.length+" videos - Too many videos, URL is too long. Some videos in the playlist link may not work.";
	} else {
		playlistElements.count.classList.remove("warning");
		playlistElements.link.href="https://www.youtube.com/watch_videos?video_ids="+videoIds.join(',');
		playlistElements.plain.value=videoIds.join(',');
		playlistElements.count.textContent = ""+videoIds.length+" videos";
	}
}

// clear all video IDs, reset buttons
function clearPlaylist() {
	videoIds = [];
	var buttons = document.querySelectorAll('#browse-items-primary > .section-list .subs2playlist');
	for (var i = 0; i < buttons.length; ++i) {
		buttons[i].classList.remove("subs2playlist-remove");
		//buttons[i].classList.remove("yt-uix-button-subscribed-branded");
		buttons[i].classList.remove("c4-module-editor-delete");
		buttons[i].classList.add("subs2playlist-add");
		buttons[i].classList.add("c4-editor-plus");
	}
	updatePlaylistLink();
}

// only add videos that aren't duplicates
function toggleVideoId(videoId){
	var alreadyIn = videoIds.indexOf(videoId);
	if(alreadyIn === -1){
		videoIds.push(videoId);
		return true;
	} else {
		videoIds.splice(alreadyIn, 1);
		return false;
	}
}

// add a button to a subscription list item
function createPlusButton(videoId){
	var container = document.createElement('button'); 
	container.setAttribute('onclick', 'toggleVideoId(\''+videoId+'\');');
	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";
	container.onclick = function(){ 
		toggleVideoId(videoId);
		container.classList.toggle("c4-module-editor-delete");
		container.classList.toggle("c4-editor-plus");
		container.classList.toggle("subs2playlist-remove");
		container.classList.toggle("subs2playlist-add");
		updatePlaylistLink();
	};
	container.innerHTML = "<span class=\"yt-uix-button-content\"></span>";
	return container;
}

// iterate subscription list items, then add buttons
function appendAllTheThings(node) {
	var videoId = node.getAttribute("data-context-item-id");
	var plusButtonNode = createPlusButton(videoId);
	node.appendChild(plusButtonNode);
}

var observerConfig = { 
  childList: true,
  attributes: true, 
  subtree: false,
  attributeOldValue: false
};

// add buttons to items loaded after clicking "load more" or endless scroll, which are dynamically added 
var listObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if(mutation.type == "childList" && mutation.addedNodes.length >= 1) {
      console.log(mutation);
      for (var i = 0; i < mutation.addedNodes.length; ++i) {
      	var node = mutation.addedNodes[i];
      	if(node.nodeType === 1){
      		var videoLinkContainer = node.querySelector('.yt-lockup');
      		appendAllTheThings(videoLinkContainer);
      	}
      }
    }
  });    
});
var subListContainer = document.querySelector('#browse-items-primary > .section-list');
listObserver.observe(subListContainer, observerConfig);

// first run: create our container, add buttons to playlist items
function firstRun(){
	var videoLinkNodes = document.querySelectorAll('div.yt-lockup');
	for (var i = 0; i < videoLinkNodes.length; ++i) {
		var node = videoLinkNodes[i];
		appendAllTheThings(node);
	}
	playlistElements = createPlaylistContainer();
	document.body.appendChild(playlistElements.container);
}

firstRun();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址