Export Youtube Playlist in tab delimited text

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

当前为 2021-05-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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");
    });
  }

}) ();