Greasy Fork is available in English.

Export Youtube Playlist in plaintext

Shows a list of the playlist video names/channels/URLs in plaintext to be easily copied

// ==UserScript==
// @name         Export Youtube Playlist in plaintext
// @namespace    1N07
// @version      0.7.4
// @description  Shows a list of the playlist video names/channels/URLs in plaintext to be easily copied
// @author       1N07
// @license      unlicense
// @compatible   firefox Tested on Firefox v122.0 and Tampermonkey 5.0.1
// @compatible   firefox Likely to work on other userscript managers, but not tested
// @compatible   chrome Latest version untested, but likely works with at least Tampermonkey
// @compatible   opera Latest version untested, but likely works with at least Tampermonkey
// @compatible   edge Latest version untested, but likely works with at least Tampermonkey
// @compatible   safari Latest version untested, but likely works with at least Tampermonkey
// @icon         https://www.google.com/s2/favicons?domain=youtube.com
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @match        https://www.youtube.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';

	var getVideoTitle = GM_getValue("getVideoTitle", true);
	var getVideoChannel = GM_getValue("getVideoChannel", false);
	var getVideoURL = GM_getValue("getVideoURL", false);
    var videoListSeperator = GM_getValue("videoListSeperator", "|:|");

    var listCreationAllowed = true;
    var urlAtLastCheck = "";

    //add some CSS
    if(true) {
        addGlobalStyle(`
			tp-yt-paper-listbox#items { overflow-x: hidden; }

			#exportPlainTextList {
				cursor: pointer;
				height: 36px;
				width: 100%;
				display: flex;
				align-items: center;
			}
			#exportPlainTextList > img {
				height: 24px; width: 24px;
				color: rgb(144, 144, 144);
				padding: 0 13px 0 16px;
				filter: contrast(0%);
			}
			#exportPlainTextList > span {
				font-family: "Roboto","Arial",sans-serif;
				color: var(--yt-spec-text-primary);
				white-space: nowrap;
				font-size: 1.4rem;
				line-height: 2rem;
				font-weight: 400;
			}

			#exportPlainTextList:hover { background-color: rgba(255,255,255,0.1); }
			ytd-menu-popup-renderer.ytd-popup-container { overflow-x: hidden !important; max-height: none !important; }

			#listDisplayContainer {
				position: fixed;
				z-index: 9999;
				margin: 0 auto;
				background-color: #464646;
				padding: 10px;
				border-radius: 5px;
				left: 0;
				right: 0;
				max-width: 100vw;
				width: 1200px;
				height: 900px;
				max-height: 90vh;
				top: 5vh;
                resize: both;
                overflow: hidden;
			}
			#listDisplayContainer > textarea {
				box-sizing: border-box;
				width: 100%;
				margin: 10px 0;
				height: calc(100% - 40px);
				background-color: #262626;
				border: none;
				color: #EEE;
				border-radius: 5px;
                resize: none;
			}
			#closeTheListThing {
				float: right;
				font-weight: bold;
				background-color: RGBA(255,255,255,0.25);
				border: none;
				font-size: 17px;
				border-radius: 10px;
				height: 25px;
				width: 25px;
				cursor: pointer;
			}

			#closeTheListThing:hover { background-color: rgba(255,255,255,0.5); }
		`);
    }

    setInterval(function(){
        if(urlAtLastCheck != window.location.href)
        {
            urlAtLastCheck = window.location.href;
            if(urlAtLastCheck.includes("/playlist?list="))
                InsertButtonASAP();
        }
    }, 100);


    function InsertButtonASAP()
    {
        let buttonInsertInterval = setInterval(function(){
            //wait for possible previous buttons to stop existing (due to how youtube loads pages) and for the space for the new button to be available
            if($("#exportPlainTextList").length == 0 && $("tp-yt-paper-listbox#items").length > 0)
            {
                $("tp-yt-paper-listbox#items").append(`
					<div id="exportPlainTextList">
						<img src="https://i.imgur.com/emlur3a.png">
						<span>Export Playlist</span>
					</div>
				`);
                $("#exportPlainTextList").click(ScrollUntillAllVisible);
                setTimeout(function() { clearInterval(buttonInsertInterval); }, 5000);
            }
        }, 100);
    }

    function ScrollUntillAllVisible()
    {
        if(!listCreationAllowed)
            return;

        $("ytd-browse[page-subtype='playlist']").click();

        listCreationAllowed = false;
        $("#exportPlainTextList").after(`<p id="listBuildMessage" style="color: red; font-size: 1.33em;">Getting list...<br>please click out of the popup to continue autoscrolling...</p>`);
        let scrollInterval = setInterval(function(){
            if($("ytd-continuation-item-renderer.ytd-playlist-video-list-renderer").length)
                $(document).scrollTop($(document).height());
            else
            {
                $("#listBuildMessage").remove();
                DisplayListOptions();
                clearInterval(scrollInterval);
            }
        }, 100);
    }

    function DisplayListOptions() {
        $("body").append(`
			<div id="listDisplayContainer">
				<p style="text-align: center;">
					<span style="font-size: 21px; font-weight: bold; color: #d9d9d9;">Playlist in plain text</span>
					<button id="closeTheListThing">X</button>
				</p>
				<textarea style="display: none;">`+list+`</textarea>
                <ul id="listDisplayOptions" style="list-style: none; font-size: 12px; scale: 1.4; color: #d9d9d9; width: -moz-fit-content; width: fit-content; margin: 40px auto;">
                    <li><label><input type="checkbox" ` + (getVideoTitle ? `checked` : ``) + ` id="getVideoTitleCB" name="getVideoTitleCB" value="getVideoTitle"> Get titles</label></li>
                    <li><label><input type="checkbox" ` + (getVideoChannel ? `checked` : ``) + ` id="getVideoChannelCB" name="getVideoChannelCB" value="getVideoChannel"> Get channel names</label></li>
                    <li><label><input type="checkbox" ` + (getVideoURL ? `checked` : ``) + ` id="getVideoURLCB" name="getVideoURLCB" value="getVideoURL"> Get URLs</label></li>
                    <li><label><input type="text" style="width: 40px; text-align: center;" id="videoListSeperatorInput" name="videoListSeperatorInput" value="`+videoListSeperator+`"> Name/Author/URL seperator</label></li>
                    <li><button id="listDisplayGetListButton" style="position: relative; margin: 10px 0; font-size: 13px; left: 50%; -ms-transform: translateX(-50%); transform: translateX(-50%);">Get list</button></li>
                </ul>
			</div>
		`);

        $("#getVideoTitleCB").change(function() {
            getVideoTitle = $(this).is(":checked");
            GM_setValue("getVideoTitle", getVideoTitle);
        });
        $("#getVideoChannelCB").change(function() {
            getVideoChannel = $(this).is(":checked");
            GM_setValue("getVideoChannel", getVideoChannel);
        });
        $("#getVideoURLCB").change(function() {
            getVideoURL = $(this).is(":checked");
            GM_setValue("getVideoURL", getVideoURL);
        });
        $("#videoListSeperatorInput").change(function() {
            videoListSeperator = $(this).val();
            GM_setValue("videoListSeperator", videoListSeperator);
        });
        $("#listDisplayGetListButton").click(BuildAndDisplayList);
        $("#closeTheListThing").click(function(){
            $("#listDisplayContainer").remove();
            listCreationAllowed = true;
        });
    }

    function BuildAndDisplayList()
    {
        $("#listDisplayOptions").hide();
        $("#listDisplayContainer > textarea").show();

		let videoTitleArr = [];
		let videoChannelArr = [];
		let videoURLArr = [];
		let videoCount = 0;

		$("ytd-playlist-video-list-renderer > #contents.ytd-playlist-video-list-renderer > ytd-playlist-video-renderer #content").each(function(){
			if(getVideoTitle)
				videoTitleArr.push($(this).find("#video-title").attr("title"));

			if(getVideoURL)
				videoURLArr.push("https://www.youtube.com" + $(this).find("#video-title").attr("href").split("&")[0]);

			if(getVideoChannel)
				videoChannelArr.push($(this).find("#channel-name yt-formatted-string.ytd-channel-name > a").text());

			videoCount++;
		});


		let list = "";
		for(let i = 0; i < videoCount; i++)
		{
			if(getVideoTitle)
				list += videoTitleArr[i];

			if(getVideoChannel)
				list += (getVideoTitle ? " "+videoListSeperator+" " : "") + videoChannelArr[i];

			if(getVideoURL)
				list += (getVideoTitle || getVideoChannel ? " "+videoListSeperator+" " : "") + videoURLArr[i];

            list += "\n";
		}

        $("#listDisplayContainer > textarea").html(list);
        $("#listBuildMessage").remove();
    }

    function addGlobalStyle(css)
    {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }
})();