Export Youtube Playlist in tab delimited text

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

Fra 03.05.2021. Se den seneste versjonen.

// ==UserScript==
// @name          Export Youtube Playlist in tab delimited text
// @description   Creates the current playlist as tab delimited text to be easily copied
// @author        1N07 & MK
// @namespace     max44
// @homepage      https://greasyfork.org/en/users/309172-max44
// @include       https://www.youtube.com/*
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @version       0.5
// @note          v0.5 2021-05-04 - playlists with hidden unavailable videos are processed correctly
// @note          v0.4.7 2021-05-03 - error, when button disappears after switching hide/show unavailable videos, is corrected
// @note          v0.4.6 2021-04-19 - error, when some videos with "watched" status were missed from exported data, is corrected
// @note          v0.4.5 2021-03-07 - timestamp is removed from video URL
// @note          v0.4.4 2021-03-07 - video URL is added to exported data
// @note          v0.4.2 2021-02-17 - amendments to recognized new CSS code for video name
// @note          v0.4.1 2020-12-14 - various bugfixes and improvements
// @note          v0.4 2020-12-14 - initial fork
// ==/UserScript==

(function() {
  'use strict';

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

  function InsertButtonASAP() {
    $("#exportTabTextList").remove(); //Remove previous button

    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 ($("#exportTabTextList").length == 0 && $("ytd-playlist-sidebar-secondary-info-renderer > #owner-container").length > 0) {
        $("ytd-playlist-sidebar-secondary-info-renderer > #owner-container").parent().after("<button id='exportTabTextList' style='font-family: Roboto, Arial, sans-serif; font-size: 13px;'>Export as tab delimited text</button>");

        //Check whether unavailable videos are hidden or not
        var i;
        var strAux = "";
        var flgHidden = false;
        var myNodeList = document.querySelectorAll("#text");
        for (i = 0; i < myNodeList.length; i++) {
          if (myNodeList[i].className.indexOf("style-scope ytd-alert-with-button-renderer") > -1) {
            strAux = myNodeList[i].innerText;
            strAux = strAux.trim();
            strAux = strAux.toLowerCase();
            if (strAux.indexOf("unavailable videos are hidden") > -1) {
              flgHidden = true;
              break;
            }
          }
        }
        if (flgHidden) {
          $("#exportTabTextList").click(ScrollAsPossible);
        } else {
          $("#exportTabTextList").click(ScrollUntilFullListVisible);
        }
        //clearInterval(buttonInsertInterval); - Do not clear interval in order to add button back if playlist is rebuilt
      }
    }, 100);
  }

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

    listCreationAllowed = false;
    $("#exportTabTextList").after(`<p id="listBuildMessage" style="color: red; font-size: 1.33em;">Getting full list, please wait...</p>`);
    let numOfVideosInPlaylist = $("ytd-playlist-sidebar-renderer.ytd-browse > #items #stats > yt-formatted-string.ytd-playlist-sidebar-primary-info-renderer:first").text().replace(/(\D+|\s+)/g, '');
    let scrollInterval = setInterval(function(){
      if ($("yt-formatted-string#index.ytd-playlist-video-renderer").last().text() != numOfVideosInPlaylist)
        $(document).scrollTop($(document).height());
      else {
        BuildAndDisplayList();
        clearInterval(scrollInterval);
      }
    }, 100);
  }

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

    listCreationAllowed = false;
    $("#exportTabTextList").after(`<p id="listBuildMessage" style="color: red; font-size: 1.33em;">Getting list without unavailable videos, please wait...</p>`);
    let lastHeight = 0;
    $(document).scrollTop($(document).height());
    let scrollInterval2 = setInterval(function(){
      if ($(document).height() > lastHeight) {
        lastHeight = $(document).height();
        $(document).scrollTop($(document).height());
      } else {
        BuildAndDisplayList();
        clearInterval(scrollInterval2);
      }
    }, 5000);
  }

  function BuildAndDisplayList() {
    let list = "<Name>\t<Channel>\t<Duration>\t<URL>";
    var myNodeList = document.querySelectorAll("div");
    var i;
    for (i = 0; i < myNodeList.length; i++) {
      if (myNodeList[i].id == "content" && myNodeList[i].className.indexOf("style-scope ytd-playlist-video-renderer") > -1) {
        var mySpanList = myNodeList[i].querySelectorAll("span");
        var myAList = myNodeList[i].querySelectorAll("a");
        var j;
        var strAux = "";
        var strAux2 = "";
        for (j = 0; j < myAList.length; j++) {
          if (myAList[j].id == "video-title") {
            strAux = myAList[j].innerText; //Video title
            strAux = strAux.replace(/[\x0D\x0A]/g, " ");
            list += "\n" + strAux.trim();
            strAux2 = myAList[j].href; //Video URL
            strAux2 = strAux2.replace(/&list=.*&index=\d+/gi, ""); //Remove reference to list and video's index
            strAux2 = strAux2.replace(/&t=.*$/gi, ""); //Remove timestamp
          }
        }
        list += "\t";
        for (j = 0; j < myAList.length; j++) {
          if (myAList[j].className == "yt-simple-endpoint style-scope yt-formatted-string") {
            strAux = myAList[j].innerText; //Channel name
            strAux = strAux.replace(/[\x0D\x0A]/g, " ");
            list += strAux.trim();
          }
        }
        list += "\t ";
        for (j = 0; j < mySpanList.length; j++) {
          if (mySpanList[j].className == "style-scope ytd-thumbnail-overlay-time-status-renderer") {
            strAux = mySpanList[j].innerText; //Duration
            strAux = strAux.replace(/[\x0D\x0A]/g, " ");
            list += strAux.trim();
          }
        }
        list += "\t" + strAux2.trim(); //Video URL is the last column
      }
    }

    $("body").append('<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><br><br><textarea id="tabPlayList" style="width: 50vw; height: 80vh; max-width: 90vw; max-height: 90vh;">'+list+'</textarea></div>');
    $("#listBuildMessage").remove();
    $("#closeTheListThing").click(function() {
      $("#tablistDisplayContainer").remove();
      listCreationAllowed = true;
    });
    $("#selectAllAndCopy").click(function() {
      document.getElementById("tabPlayList").select();
      document.execCommand("copy");
    });
  }

}) ();