Letterboxd Bio Modifier

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

  1. // ==UserScript==
  2. // @name Letterboxd Bio Modifier
  3. // @namespace https://github.com/soyguijarro/userscripts
  4. // @description Adds visual bio summary and Wikipedia link to actors and directors pages
  5. // @copyright 2015, Ramón Guijarro (http://soyguijarro.com)
  6. // @homepageURL https://github.com/soyguijarro/userscripts
  7. // @supportURL https://github.com/soyguijarro/userscripts/issues
  8. // @icon https://raw.githubusercontent.com/soyguijarro/userscripts/master/img/letterboxd_icon.png
  9. // @license GPLv3; http://www.gnu.org/licenses/gpl.html
  10. // @version 1.2
  11. // @include *://letterboxd.com/director/*
  12. // @include *://letterboxd.com/actor/*
  13. // @grant GM_addStyle
  14. // @grant GM_xmlhttpRequest
  15. // ==/UserScript==
  16.  
  17. var sidebarElt = document.getElementsByClassName("sidebar")[0],
  18. bioElt = sidebarElt.getElementsByClassName("tmdb-person-bio")[0],
  19. tmdbId = bioElt.getAttribute("data-tmdb-id"),
  20. tmdbBaseUrl = "http://themoviedb.org/person/",
  21. tmdbUrl = tmdbBaseUrl + tmdbId;
  22.  
  23. function showBioSummary(res) {
  24. var dom = new DOMParser().parseFromString(res.responseText, "text/html"),
  25. tmdbBirthplaceElt = dom.getElementById("place_of_birth"),
  26. tmdbBirthdayElt = dom.getElementById("birthday"),
  27. tmdbDeathdayElt = dom.getElementById("deathday"),
  28. creditsElt = dom.getElementById("leftCol").getElementsByTagName("p")[0],
  29. creditsMatch = creditsElt.textContent.match(/Known Credits: (\d+)/),
  30. gotRelevantData = isActualData(tmdbBirthplaceElt) ||
  31. isActualData(tmdbBirthdayElt),
  32. bioSummaryElt = document.createElement("section"),
  33. bioSummaryElts,
  34. bioInnerElt,
  35. cssRules = "section.panel-text.bio-summary {\
  36. border-bottom: 1px solid #456;\
  37. }\
  38. section.panel-text.bio-summary p {\
  39. padding-left: 25px;\
  40. display: block;\
  41. }";
  42.  
  43. function getFormattedDate(date) {
  44. var monthNum = date.getMonth(),
  45. dayNum = date.getDate(),
  46. yearNum = date.getFullYear(),
  47. monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
  48. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  49.  
  50. return monthNames[monthNum] + " " + dayNum + ", " + yearNum;
  51. }
  52.  
  53. function isActualData(elt) {
  54. var data = elt.textContent,
  55. exp = /^-$/;
  56.  
  57. return !(exp.test(data));
  58. }
  59.  
  60. function showBirthplace() {
  61. var birthplace = tmdbBirthplaceElt.textContent,
  62. birthplaceElt = document.createElement("p"),
  63. birthplaceIconElt = document.createElement("span");
  64.  
  65. // Fill element with data and apply styles
  66. birthplaceElt.classList.add("icon-location");
  67. birthplaceElt.textContent = birthplace.replace(/ - /g, ", ");
  68. birthplaceIconElt.style.marginLeft = "2px";
  69.  
  70. // Insert element in section
  71. birthplaceElt.appendChild(birthplaceIconElt);
  72. bioSummaryElt.appendChild(birthplaceElt);
  73. }
  74.  
  75. function showBornDeathDate() {
  76. var birthday = new Date(tmdbBirthdayElt.firstElementChild.textContent),
  77. dateElt = document.createElement("p"),
  78. dateIconElt = document.createElement("span"),
  79. msPerYear = 1000 * 60 * 60 * 24 * 365,
  80. refDate,
  81. date,
  82. age;
  83.  
  84. // Fill element with data and apply styles
  85. if (tmdbDeathdayElt) { // Person is dead
  86. // Use death date as reference to calculate age
  87. refDate = new Date(tmdbDeathdayElt.firstElementChild.textContent);
  88. date = refDate; // Show death date
  89.  
  90. dateElt.classList.add("icon-hidden");
  91. dateIconElt.style.marginLeft = "3px";
  92. } else { // Person is alive
  93. // Use today as reference to calculate age
  94. refDate = new Date();
  95. date = birthday; // Show birthday
  96.  
  97. dateElt.classList.add("icon-people");
  98. }
  99. age = Math.floor((refDate - birthday.getTime()) / msPerYear);
  100. dateElt.textContent = getFormattedDate(date) +
  101. " (age" + ((tmdbDeathdayElt) ? "d " : " ") + age + ")";
  102.  
  103. // Insert element in section
  104. dateElt.appendChild(dateIconElt);
  105. bioSummaryElt.appendChild(dateElt);
  106. }
  107.  
  108. function showNumCredits() {
  109. var numCredits = creditsMatch[1],
  110. creditsElt = document.createElement("p"),
  111. creditsIconElt = document.createElement("span");
  112.  
  113. // Fill element with data and apply styles
  114. creditsElt.classList.add("icon-list-all");
  115. creditsElt.textContent = numCredits + " known credits";
  116. creditsIconElt.style.backgroundPosition = "-740px -110px";
  117.  
  118. // Insert element in section
  119. creditsElt.appendChild(creditsIconElt);
  120. bioSummaryElt.appendChild(creditsElt);
  121. }
  122.  
  123. // Set up section to be inserted in page
  124. bioSummaryElt.className = "section panel-text bio-summary";
  125.  
  126. // Fill section with available data
  127. if (gotRelevantData) {
  128. if (isActualData(tmdbBirthplaceElt)) {
  129. showBirthplace();
  130. }
  131. if (isActualData(tmdbBirthdayElt)) {
  132. showBornDeathDate();
  133. }
  134. if (creditsMatch) {
  135. showNumCredits();
  136. }
  137. } else {
  138. return; // Abort if no relevant data at all is available
  139. }
  140.  
  141. // Apply common styles to section elements
  142. bioSummaryElts = bioSummaryElt.children;
  143. for (var i = 0; i < bioSummaryElts.length; i++) {
  144. bioSummaryElts[i].classList.add("has-icon")
  145. bioSummaryElts[i].classList.add("icon-16");
  146. bioSummaryElts[i].firstElementChild.className = "icon";
  147. }
  148.  
  149. // Insert section in page
  150. bioInnerElt = bioElt.getElementsByClassName("panel-text condensed")[0] ||
  151. bioElt.getElementsByClassName("panel-text")[0];
  152.  
  153. if (bioInnerElt) { // Already existing bio section
  154. bioInnerElt.insertBefore(bioSummaryElt, bioInnerElt.firstElementChild.nextSibling);
  155. } else { // No bio section, add missing header
  156. var bioHeaderElt = document.createElement("h2");
  157. bioHeaderElt.className = "section-heading";
  158. bioHeaderElt.textContent = "Bio";
  159.  
  160. bioElt.appendChild(bioHeaderElt);
  161. bioElt.appendChild(bioSummaryElt);
  162. }
  163. GM_addStyle(cssRules);
  164. }
  165.  
  166. function showWikiLink() {
  167. var linksElt = document.getElementsByClassName("bio-link")[0],
  168. headerElt = document.getElementsByClassName("page-header")[0],
  169. personNameElt = headerElt.querySelector("h1.inline-heading.prettify em"),
  170. wikiLinkElt = document.createElement("li"),
  171. wikiLinkInnerElt = document.createElement("a"),
  172. personName = personNameElt.textContent,
  173. wikiBaseUrl = "https://en.wikipedia.org/wiki/",
  174. wikiUrl = wikiBaseUrl + personName,
  175. linksInnerElt;
  176.  
  177. // Fill element with data and apply styles
  178. wikiLinkInnerElt.className = "box-link";
  179. wikiLinkInnerElt.href = wikiUrl;
  180. wikiLinkInnerElt.textContent = "Search on Wikipedia";
  181. wikiLinkElt.appendChild(wikiLinkInnerElt);
  182.  
  183. // Insert section in page
  184. if (linksElt) { // Already existing link section
  185. linksInnerElt = linksElt.getElementsByTagName("ul")[0];
  186. linksInnerElt.insertBefore(wikiLinkElt, linksInnerElt.firstElementChild);
  187. } else { // No link section, create first
  188. linksElt = document.createElement("section");
  189. linksInnerElt = document.createElement("ul");
  190. linksElt.className = "section bio-link";
  191. linksInnerElt.className = "box-link-list box-links";
  192.  
  193. linksInnerElt.appendChild(wikiLinkElt);
  194. linksElt.appendChild(linksInnerElt);
  195. sidebarElt.insertBefore(linksElt,
  196. sidebarElt.getElementsByClassName("progresspanel")[0]);
  197. }
  198. }
  199.  
  200. GM_xmlhttpRequest({
  201. method: "GET",
  202. url: tmdbUrl,
  203. onload: showBioSummary
  204. });
  205.  
  206. showWikiLink();