IMDb Actor Age/IMDBAge

Display the actor's age next to movie credits / Adds the age and other various info onto IMDB pages

// ==UserScript==
// @name        IMDb Actor Age/IMDBAge
// @description Display the actor's age next to movie credits / Adds the age and other various info onto IMDB pages
// @match       *://*.imdb.com/name/nm*
// @match       *://*.imdb.com/title/tt*
// @version     10.0.2
// @grant       GM_setValue
// @grant       GM_getValue
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @namespace http://www.stewarts.org.uk
// ==/UserScript==

var LOGGING = false;
function log(...msg) {
  LOGGING && console.log(...msg);
}

(function() {
  'use strict';

  /// ACTOR PAGES ///
  if (window.location.href.match(/\/name\/nm\d+\/(reference|\?|$)/)) {
    let actorNum = window.location.href.match(/\/name\/nm(\d+)/)[1];
    let birthday = document.querySelectorAll("[data-testid='birth-and-death-birthdate'] span:nth-of-type(2)");
    let birthTime = (birthday && birthday[1].textContent.trim()) || GM_getValue(actorNum);
    let birthMoment = moment(birthTime);
    log('birthTime', birthTime, 'mom', birthMoment);

    if (birthTime) {
      GM_setValue(actorNum, birthMoment.format('YYYY-MM-DD'));
      if (birthday) {
        let ageSpan = getAgeSpan(birthMoment, moment());
        birthday.forEach((item) => {
          item.appendChild(ageSpan);
        });
      }
      let yearCols = document.querySelectorAll('div.ipc-metadata-list-summary-item__cc ul li span.ipc-metadata-list-summary-item__li');
      yearCols.forEach((yc) => {
        log('yc', yc);
        let br = yc.parentNode.querySelector('span');
        let years = yc.textContent.trim();
        log('years', years);
        log('br', br)
        if (years && years.length > 3 && br) {
          let ageSpan = getAgeSpan(birthMoment, years);
          br.parentNode.appendChild(ageSpan);
        }
      });
      (new MutationObserver(updateActor)).observe(document.querySelector("[data-testid='Filmography']"), { attributes: false, characterData: false, childList: true, subtree: true, attributeOldValue: false, characterDataOldValue: false });
      function updateActor(changes) {
        log(changes)
        changes.forEach(mutation => {
          const newNodes = mutation.addedNodes;
          newNodes.forEach(node => {
            if (node.classList && node.classList.contains('ipc-metadata-list-summary-item')) {
              let yc = node.querySelector('div.ipc-metadata-list-summary-item__cc ul li span.ipc-metadata-list-summary-item__li');
              log('yc', yc);
              let br = yc.parentNode.querySelector('span');
              let years = yc.textContent.trim();
              log('years', years);
              log('br', br)
              if (years && years.length > 3 && br) {
                let ageSpan = getAgeSpan(birthMoment, years);
                br.parentNode.appendChild(ageSpan);
              }
            }
          });
        });
      }
    }

    /// MOVIE PAGES ///
  } else if (window.location.href.match(/\/title\/tt\d+\/(reference|fullcredits)?/)) {
    let rows = document.querySelectorAll("[data-testid='title-cast-item'], .cast_list tr.odd, .cast_list tr.even");
    let m = document.title.match(/\((?:TV\s+(?:(?:Mini\s)?Series|Episode|Movie)\s*)?(\d{4}\s*(?:–|-)?\s*(?:\d{4})?)\s*\)/);
    if (m) {
      let titleYears = m[1];
      addFilmAge(titleYears);
      rows.forEach((el) => {
        log(el);
        let actorLink = el.querySelector('a');
        log(actorLink);
        let m = actorLink && actorLink.href.match(/\/name\/nm(\d+)/);
        if (m) {
          let actorNum = m[1];
          log(actorNum);
          let birthTime = GM_getValue(actorNum);
          log(birthTime);
          if (birthTime) {
            let ageSpan = getAgeSpan(moment(birthTime), titleYears);
            log(ageSpan);
            el.querySelectorAll('td > a, div > a')[1].after(ageSpan);//.appendChild(ageSpan);
          }
        }
      });
    }
  }

  function getAgeSpan(birth, years) {
    log('getAgeSpan', birth, years);
    if (typeof years == "string") {
      var m = years.match(/(\d{4})(?:–|-)?(\d{4})?/);
    } else {
      m = [ years.format('YYYY-MM-dd') ];
    }
    log('match m', m, 'moment', moment(m[1]));
    let ageString = moment(m[1]).diff(birth, 'years');
    if (m[2]) {
      ageString += '-' + moment(m[2]).diff(birth, 'years');
    }
    ageString = '(Age ' + ageString + ')';
    let ageSpan = document.createElement('div');
    ageSpan.classList.add('ageSpan');
    ageSpan.textContent = ageString;
    return ageSpan;
  }

  function addFilmAge(years) {
    let m = years.match(/(\d{4})(?:–|-)?(\d{4})?/);
    let year = new Date();
    year.setFullYear(m[1]);
    let age = new Date().getFullYear() - year.getFullYear();

    if (m[2]) {
      let year2 = new Date();
      year2.setFullYear(m[2]);
      var age2 = new Date().getFullYear() - year2.getFullYear();
    }

    let container;
    if (age >= 1) { container = ("" + age + " year" + (age == 1 ? '' : 's') + " ago"); }
    if (age == 0) { container = ("This year"); }
    if (age <= -1) { container = ("in " + Math.abs(age) + " year" + (Math.abs(age) == 1 ? '' : 's')); }

    if(age2) {
      container += " to ";
      if (age2 >= 1) { container += ("" + age2 + " year" + (age2 == 1 ? '' : 's') + " ago"); }
      if (age2 == 0) { container += ("This year"); }
      if (age2 <= -1) { container += ("in " + Math.abs(age2) + " year" + (Math.abs(age2) == 1 ? '' : 's')); }
    }

    let ageSpan = document.createElement('li');
    ageSpan.classList.add('ipc-inline-list__item');
    ageSpan.textContent = container;
    if(document.querySelector("[data-testid='hero__pageTitle']") && document.querySelector("[data-testid='hero__pageTitle']").nextSibling.querySelector("li")) {
      document.querySelector("[data-testid='hero__pageTitle']").nextSibling.querySelector("li").after(ageSpan);
    } else if(document.querySelector("[data-testid='hero__pageTitle']") && document.querySelector("[data-testid='hero__pageTitle']").nextSibling.nextSibling.querySelector("li")) {
      document.querySelector("[data-testid='hero__pageTitle']").nextSibling.nextSibling.querySelector("li").after(ageSpan);
    }
  }

  let s = document.createElement('style');
  s.textContent = `
.ageSpan {
  font-size: 9px !important;
  font-weight: bold;
}
`;
  document.body.appendChild(s);
})();