YouTube Playlist Page - Show Date Posted and View Count

Show date posted and view count on youtube playlist page

// ==UserScript==
// @name         YouTube Playlist Page - Show Date Posted and View Count
// @namespace    http://james0x57.com/
// @version      0.1
// @description  Show date posted and view count on youtube playlist page
// @author       James0x57
// @match        https://www.youtube.com/playlist*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
  'use strict';

  function addCSS(css) {
    var el = document.createElement('div');
    el.innerHTML = '<b>CSS</b><style type="text/css">' + css + '</style>';
    el = el.childNodes[1];
    if (el) document.getElementsByTagName('head')[0].appendChild(el);
    return el;
  }

  // https://gist.github.com/James0x57/da84cc2bb6087db5f041387b0a586e6c
  //## Begin Selector Observation Code
    var selectors = [];
    (new MutationObserver(
      function (mutationsList) {
        var s, selector, nodeMatches
        var slen = selectors.length
        for (s = 0; s < slen; s++) {
          selector = selectors[s]
          nodeMatches = node => node.nodeType === 1 && node.matches(selector.childSelector)

          mutationsList.forEach(mu => {
            if (mu.type === "childList" && mu.target.matches(selector.parentSelector)) {
              var addedMatches = Array.prototype.filter.call(mu.addedNodes, nodeMatches)
              var removedMatches = Array.prototype.filter.call(mu.removedNodes, nodeMatches)
              addedMatches.length && selector.inserted.call(null, addedMatches)
              removedMatches.length && selector.removed.call(null, removedMatches)
            }
          })
        }
      }
    )).observe(document.documentElement, {
      childList: true,
      subtree: true
    })

    var onParentChildSelectors = function (opts) {
      var nullFn = () => {}
      selectors.push(Object.assign({
        parentSelector: "",
        childSelector: "",
        inserted: nullFn,
        removed: nullFn
      }, opts))
    }
  //## End Selector Observation Code

  addCSS(`
    #video-title.ytd-playlist-video-renderer[aria-label]:after {
      content: attr(data-jca-meta-info);
      font-weight: normal;
      font-size: 12px;
      display: block;
    }
  `)

  var handleVideoInList = function (videoListItemEl) {
    var titleEl = videoListItemEl.querySelector("#video-title.ytd-playlist-video-renderer[aria-label]")
    if (!titleEl) {
      return
    }
    var hiddenData = titleEl.getAttribute("aria-label")
    var time, unit, views
    hiddenData.replace(
      /.*?([0-9,]+) ([a-z]+?)s? ago.*? ([0-9,]+) views/,
      (x, ...captures) => { [time, unit, views] = captures }
    )
    var metaInfo = "Posted "
    var date = new Date()
    if (unit === "day") {
      date.setDate(date.getDate() - time)
      metaInfo += "on " + date.toString().replace(/(20\d\d) .*/, "$1")
    } else if (unit === "week") {
      date.setDate(date.getDate() - (time * 7))
      metaInfo += "week of " + date.toString().replace(/(20\d\d) .*/, "$1")
    } else if (unit === "month") {
      date.setMonth(date.getMonth() - time)
      metaInfo += "in " + date.toString().replace(/^[^ ]* ([^ ]+) .*? (20\d\d) .*/, "$1 $2")
    } else if (unit === "year") {
      date.setYear(date.getYear() - time + 1900)
      metaInfo += "in " + date.toString().replace(/^.*? (20\d\d) .*/, "$1")
    } else { // hours, min
      metaInfo += date.toString().replace(/(20\d\d) .*/, "$1")
    }
    metaInfo += " | Views: "
    metaInfo += views
    titleEl.setAttribute("data-jca-meta-info", metaInfo)
  }

  onParentChildSelectors({
    parentSelector: "ytd-playlist-video-list-renderer #contents",
    childSelector: "ytd-playlist-video-renderer",
    inserted: videoListItemEls => videoListItemEls.forEach(handleVideoInList)
  })
})()