Letterboxd Bio Modifier

Adds visual bio summary and Wikipedia link to actors and directors pages

// ==UserScript==
// @name        Letterboxd Bio Modifier
// @namespace   https://github.com/soyguijarro/userscripts
// @description Adds visual bio summary and Wikipedia link to actors and directors pages
// @copyright   2015, Ramón Guijarro (http://soyguijarro.com)
// @homepageURL https://github.com/soyguijarro/userscripts
// @supportURL  https://github.com/soyguijarro/userscripts/issues
// @icon        https://raw.githubusercontent.com/soyguijarro/userscripts/master/img/letterboxd_icon.png
// @license     GPLv3; http://www.gnu.org/licenses/gpl.html
// @version     1.2
// @include     *://letterboxd.com/director/*
// @include     *://letterboxd.com/actor/*
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// ==/UserScript==

var sidebarElt = document.getElementsByClassName("sidebar")[0],
    bioElt = sidebarElt.getElementsByClassName("tmdb-person-bio")[0],
    tmdbId = bioElt.getAttribute("data-tmdb-id"),
    tmdbBaseUrl = "http://themoviedb.org/person/",
    tmdbUrl = tmdbBaseUrl + tmdbId;

function showBioSummary(res) {
    var dom = new DOMParser().parseFromString(res.responseText, "text/html"),
        tmdbBirthplaceElt = dom.getElementById("place_of_birth"),
        tmdbBirthdayElt = dom.getElementById("birthday"),
        tmdbDeathdayElt = dom.getElementById("deathday"),
        creditsElt = dom.getElementById("leftCol").getElementsByTagName("p")[0],
        creditsMatch = creditsElt.textContent.match(/Known Credits: (\d+)/),
        gotRelevantData = isActualData(tmdbBirthplaceElt) ||
            isActualData(tmdbBirthdayElt),
        bioSummaryElt = document.createElement("section"),
        bioSummaryElts,
        bioInnerElt,
        cssRules = "section.panel-text.bio-summary {\
                        border-bottom: 1px solid #456;\
                    }\
                    section.panel-text.bio-summary p {\
                        padding-left: 25px;\
                        display: block;\
                    }";

    function getFormattedDate(date) {
        var monthNum = date.getMonth(),
            dayNum = date.getDate(),
            yearNum = date.getFullYear(),
            monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

        return monthNames[monthNum] + " " + dayNum + ", " + yearNum;
    }

    function isActualData(elt) {
        var data = elt.textContent,
            exp = /^-$/;

        return !(exp.test(data));
    }

    function showBirthplace() {
        var birthplace = tmdbBirthplaceElt.textContent,
            birthplaceElt = document.createElement("p"),
            birthplaceIconElt = document.createElement("span");

        // Fill element with data and apply styles
        birthplaceElt.classList.add("icon-location");
        birthplaceElt.textContent = birthplace.replace(/ - /g, ", ");
        birthplaceIconElt.style.marginLeft = "2px";

        // Insert element in section
        birthplaceElt.appendChild(birthplaceIconElt);
        bioSummaryElt.appendChild(birthplaceElt);
    }

    function showBornDeathDate() {
        var birthday = new Date(tmdbBirthdayElt.firstElementChild.textContent),
            dateElt = document.createElement("p"),
            dateIconElt = document.createElement("span"),
            msPerYear = 1000 * 60 * 60 * 24 * 365,
            refDate,
            date,
            age;

        // Fill element with data and apply styles
        if (tmdbDeathdayElt) {  // Person is dead
            // Use death date as reference to calculate age
            refDate = new Date(tmdbDeathdayElt.firstElementChild.textContent);
            date = refDate; // Show death date

            dateElt.classList.add("icon-hidden");
            dateIconElt.style.marginLeft = "3px";
        } else {    // Person is alive
            // Use today as reference to calculate age
            refDate = new Date();
            date = birthday;    // Show birthday

            dateElt.classList.add("icon-people");
        }
        age = Math.floor((refDate - birthday.getTime()) / msPerYear);
        dateElt.textContent = getFormattedDate(date) +
            " (age" + ((tmdbDeathdayElt) ? "d " : " ") + age + ")";

        // Insert element in section
        dateElt.appendChild(dateIconElt);
        bioSummaryElt.appendChild(dateElt);
    }

    function showNumCredits() {
        var numCredits = creditsMatch[1],
            creditsElt = document.createElement("p"),
            creditsIconElt = document.createElement("span");

        // Fill element with data and apply styles
        creditsElt.classList.add("icon-list-all");
        creditsElt.textContent = numCredits + " known credits";
        creditsIconElt.style.backgroundPosition = "-740px -110px";

        // Insert element in section
        creditsElt.appendChild(creditsIconElt);
        bioSummaryElt.appendChild(creditsElt);
    }

    // Set up section to be inserted in page
    bioSummaryElt.className = "section panel-text bio-summary";

    // Fill section with available data
    if (gotRelevantData) {
        if (isActualData(tmdbBirthplaceElt)) {
            showBirthplace();
        }
        if (isActualData(tmdbBirthdayElt)) {
            showBornDeathDate();
        }
        if (creditsMatch) {
            showNumCredits();
        }
    } else {
        return; // Abort if no relevant data at all is available
    }

    // Apply common styles to section elements
    bioSummaryElts = bioSummaryElt.children;
    for (var i = 0; i < bioSummaryElts.length; i++) {
        bioSummaryElts[i].classList.add("has-icon")
        bioSummaryElts[i].classList.add("icon-16");
        bioSummaryElts[i].firstElementChild.className = "icon";
    }

    // Insert section in page
    bioInnerElt = bioElt.getElementsByClassName("panel-text condensed")[0] ||
        bioElt.getElementsByClassName("panel-text")[0];

    if (bioInnerElt) {  // Already existing bio section
        bioInnerElt.insertBefore(bioSummaryElt, bioInnerElt.firstElementChild.nextSibling);
    } else {    // No bio section, add missing header
        var bioHeaderElt = document.createElement("h2");
        bioHeaderElt.className = "section-heading";
        bioHeaderElt.textContent = "Bio";

        bioElt.appendChild(bioHeaderElt);
        bioElt.appendChild(bioSummaryElt);
    }
    GM_addStyle(cssRules);
}

function showWikiLink() {
    var linksElt = document.getElementsByClassName("bio-link")[0],
        headerElt = document.getElementsByClassName("page-header")[0],
        personNameElt = headerElt.querySelector("h1.inline-heading.prettify em"),
        wikiLinkElt = document.createElement("li"),
        wikiLinkInnerElt = document.createElement("a"),
        personName = personNameElt.textContent,
        wikiBaseUrl = "https://en.wikipedia.org/wiki/",
        wikiUrl = wikiBaseUrl + personName,
        linksInnerElt;

    // Fill element with data and apply styles
    wikiLinkInnerElt.className = "box-link";
    wikiLinkInnerElt.href = wikiUrl;
    wikiLinkInnerElt.textContent = "Search on Wikipedia";
    wikiLinkElt.appendChild(wikiLinkInnerElt);

    // Insert section in page
    if (linksElt) { // Already existing link section
        linksInnerElt = linksElt.getElementsByTagName("ul")[0];
        linksInnerElt.insertBefore(wikiLinkElt, linksInnerElt.firstElementChild);
    } else {    // No link section, create first
        linksElt = document.createElement("section");
        linksInnerElt = document.createElement("ul");
        linksElt.className = "section bio-link";
        linksInnerElt.className = "box-link-list box-links";

        linksInnerElt.appendChild(wikiLinkElt);
        linksElt.appendChild(linksInnerElt);
        sidebarElt.insertBefore(linksElt,
            sidebarElt.getElementsByClassName("progresspanel")[0]);
    }
}

GM_xmlhttpRequest({
    method: "GET",
    url: tmdbUrl,
    onload: showBioSummary
});

showWikiLink();