Youtube Subscription List Quick Playlist

Quickly create playlists from your Youtube subscription feed

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name        Youtube Subscription List Quick Playlist
// @namespace   https://greasyfork.org/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();