NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// File encoding: UTF-8
//{
// This is a script for the IMDb site. It emphasize links to movies in your
// "My Movies" and "Vote History" lists. For instance, on an actor's page,
// you'll easily notice which of his/her movies you've already seen/voted.
//
// Copyright (c) 2008-2024, Ricardo Mendonça Ferreira (ric@mpcnet.com.br)
// Released under the GPL license - http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name IMDb 'My Movies' enhancer
// @description Emphasize the links for movies and people in your IMDb lists
// @namespace http://www.flickr.com/photos/ricardo_ferreira/2502798105/
// @homepageURL https://openuserjs.org/scripts/AltoRetrato/IMDb_My_Movies_enhancer
// @copyright 2008-2024, Ricardo Mendonca Ferreira
// @license GPL-3.0-or-later
// @oujs:author Ricardo
// @include http://*.imdb.com/*
// @include https://*.imdb.com/*
// @match http://*.imdb.com/*
// @match https://*.imdb.com/*
// @exclude http://i.imdb.com/*
// @exclude http://*imdb.com/images/*
// @exclude http://*imdb.com/list/export*
// @exclude http://*imdb.com/eyeblaster/*
// @exclude https://i.imdb.com/*
// @exclude https://*imdb.com/images/*
// @exclude https://*imdb.com/list/export*
// @exclude https://*imdb.com/eyeblaster/*
// @version 1.53
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @updateURL https://openuserjs.org/meta/AltoRetrato/IMDb_My_Movies_enhancer.meta.js
// ==/UserScript==
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you either need Google Chrome (www.google.com/chrome)
// or Firefox (www.firefox.com) with Greasemonkey (www.greasespot.net).
// Install Greasemonkey, then restart Firefox and revisit this script.
//
// To uninstall, go to Tools/Greasemonkey/Manage User Scripts,
// select this script and click Uninstall.
//
// --------------------------------------------------------------------
//
// To-do:
// - Add support for other types of lists
// - Allow script configuration without source code changes
// - Make it a Chrome / Firefox plugin
// - Add more highlighting options
//
// History:
// --------
// 2024.06.04 [1.53] Fixed issue loading Watchlist and Check-Ins lists.
// 2024.06.03 [1.52] New IMDb layout, new script update.
// 2022.05.18 [1.51] New IMDb layout, new highlightTitle() & highlightLinks()
// 2021.06.18 [1.50] New release, just to fix auto update issue I might have caused in the previous version. Thanks, Martin! See https://openuserjs.org/scripts/AltoRetrato/IMDb_My_Movies_enhancer/issues/script_stopped_working_since_IMDB_new_Update
// 2021.06.09 [1.49] New IMDb layout, new highlightTitle()
// 2020.02.28 [1.48] Fixed bug where script won't work unless user has custom lists. Thanks, alienfoil!
// 2019.11.22 [1.47] New IMDb layout, new getCurrentUser()
// 2018.09.03 [1.46] New IMDb layout, new highlightTitle()
// 2018.07.10 [1.45] Better error messages & progress bar handling during errors, specially for those who never used check-ins.
// 2018.07.05 [1.44] 10 year anniversary edition! :D
// Fixed bug from v. 1.43 where movie titles and people names were getting mixed.
// New default highlight color for lists of people.
// New & improved way to set color priority (search for listOrder).
// Now supporting check-ins list.
// 2018.07.03 [1.43] Added support for lists of people - but since I don't use this feature, I'll only see problems via bug reports.
// 2018.02.16 [1.42] Included support for "https" protocol. There is one local storage for each scheme,
// so stick to either http or https browsing to avoid having to download your data twice.
// 2018.01.19 [1.41] New IMDb layout "removed" script buttons, this update fixes it.
// 2017.12.19 [1.40] IMDb FINALLY produces correct CSV files (it took them only a few years to fix this)!
// There's also a new "Your Lists" page layout, so a new version of the script is required.
// Now two steps are required to fetch the watchlist.
// 2017.03.16 [1.39] New IMDb layout, new highlightTitle()
// 2016.01.27 [1.38] New IMDb layout, new highlightTitle()
// 2014.12.19 [1.37] Work around IMDb CSV bug: https://openuserjs.org/scripts/Ricardo/IMDb_My_Movies_enhancer/issues/Script_doesnt_parse_movies_with_character_in_the_title
// 2014.10.31 [1.36] New script hosting (OpenUserJs.org); small fixes
// 2013.10.05 [1.35] Fixed bug where script buttons might not show up sometimes
// 2013.09.21 [1.33] Experimental: downloading all lists at once! (http://userscripts.org/topics/131873)
// 2013.09.20 [1.32] Fixed another change in the IMDb site (http://userscripts.org/topics/126010?page=2)
// 2013.05.31 [1.31] Adding some more error checking
// 2013.05.29 [1.30] Fixed bug in the "tabs" sections (trivia, connections, etc.)
// 2013.05.27 [1.29] Working again after changes on IMDb site; fixed tooltip bug
// that could affect navigation (thanks, somini!); fixed tooltip bug where
// the tooltip would appear behind another element; highlighting works
// on the main page of a title
// 2012.12.06 [1.28] Sorry... fixed a bug introduced in the previous bugfix. :P
// 2012.12.06 [1.27] Changed versioning model (since it wasn't working correctly on Chrome),
// fixed small bug where search results were not being highlighted.
// 2012.12.06 [1.26] Fix for IMDb site change, correctly shows how many lists it will load,
// should also work with dynamic loaded links and images
// 2011.10.06 [1.25] Workaround fixed Opera regex memory leak; Added code to give
// "color priority" for a specific list (see movieColor function)
// 2011.10.05 [1.24] Fixed bug where movie data was not captured if there were no votes for it;
// Small changes to try to fix possible memory leaks on Opera
// 2011.09.19 [1.23] Fixed & improved code to handle ratings, now should always show the
// correct rating for a movie in the tooltips
// 2011.09.17 [1.22] Small bugfixes; made it work again on Google Chrome
// 2011.09.14 [1.21] Works on xx.imdb.com; made it easier to support movies lists in
// languages other than English (if/when they are available)
// 2011.09.09 [1.20] To disable color highlighting for a list just remove its customColors
// entry, or make the color = ""; Now compatible with N900 again; Shows
// both your rating & IMDb rating in the tooltip
// 2011.09.07 [1.19] Fix for IMDb change in the format of the export link
// 2011.09.06 [1.18] Fixed bug where movies were still considered in a list when they were not
// 2011.09.04 [1.17] Using dhtml tooltips; added tooltip to movie titles;
// 2011.09.02 [1.16] Get lists id with new regex to avoid conflict with other scripts;
// Show in the link title all lists a movie is in;
// Enable custom lists colors by changing the script code (look for customColors)
// 2011.08.27c [1.15] Automatically reload page when changed sort/view
// 2011.08.27b [1.14] Don't stop downloads if can't find movies in a list; ignore lists not about titles
// 2011.08.27 [1.13] Slightly better handling of download errors
// 2011.08.26d [1.12] Fourth updade in a day! Now sorting option is user selectable
// 2011.08.26c [1.11] Guess what? Now IMDb enabled list configuration... it's not working on all lists, though... XP
// 2011.08.26b [1.10] Less than one hour after uploading this script IMDB changed a few features again! :P
// 2011.08.26 [ 1.9] Changed how lists are displayed by default; allows manual update of information
// 2011.08.13 [ 1.8] Working with new list design, using localStorage instead of GM_*Value
// 2010.06.17 [ 1.7] Added functions "missing" from Chrome; thanks, ode!
// 2009.09.23 [ 1.6] Fix for another site redesign
// 2009.08.12 [ 1.5] Restored code to deal with links like those on http://www.imdb.com/Sections/Genres/Sci-Fi/average-vote
// 2009.07.28 [ 1.4] Fix for IMDb site change, added debug information, exclude running on image URLs
// 2008.08.27 [ 1.3] Explicitly send cookies (FF3 compatibility fix)
// 2008.07.27 [ 1.2] Fixed bug where removed movies where not actually removed;
// now also highlight the title of the movies
// 2008.06.11 [ 1.1] Fixed bug that ketp growing the movie data in Firefox;
// now also get the vote history
// 2008.05.18 [ 1.0] First public release
// 2008.05.12 [ 0.1] First test version, private use only
//
//}
(function() {
var myName = 'IMDb "My Movies" Enhancer v. 1.53 (2024.06.04)'; // Name & version of this script
var myHome = 'https://openuserjs.org/scripts/AltoRetrato/IMDb_My_Movies_enhancer';
// Greasemonkey "clone functions" for Chrome (& others?), from http://userscripts.org/topics/41177
// (thanks to ode: http://userscripts.org/topics/52056?page=1#posts-261313)
// @copyright 2009, 2010 James Campos
// @license cc-by-3.0; http://creativecommons.org/licenses/by/3.0/
//
// See also:
//
// https://userscripts.org/scripts/source/145813.user.js
if (typeof GM_getValue == 'undefined') {
GM_addStyle = function(css) {
var style = document.createElement('style');
style.textContent = css;
document.getElementsByTagName('head')[0].appendChild(style);
};
GM_getValue = function(name, defaultValue) {
var value = localStorage.getItem(name);
if (!value)
return defaultValue;
var type = value[0];
value = value.substring(1);
switch (type) {
case 'b':
return value == 'true';
case 'n':
return Number(value);
default:
return value;
}
};
GM_setValue = function(name, value) {
value = (typeof value)[0] + value;
localStorage.setItem(name, value);
};
}
//----------------- End of Greasemonkey clone functions -----------------
// Modified version of Michael Leigeber's code, from:
// http://sixrevisions.com/tutorials/javascript_tutorial/create_lightweight_javascript_tooltip/
// http://userscripts.org/scripts/review/91851 & others
var injectJs = 'function tooltipClass(msg) {this.msg = msg;this.id = "tt";this.top = 3;this.left = 15;this.maxw = 500;this.speed = 10;this.timer = 20;this.endalpha = 95;this.alpha = 0;this.tt == null;this.c;this.h = 0;this.moveFunc = null;this.fade = function (d) {var a = this.alpha;if (a != this.endalpha && d == 1 || a != 0 && d == -1) {var i = this.speed;if (this.endalpha - a < this.speed && d == 1) {i = this.endalpha - a;} else if (this.alpha < this.speed && d == -1) {i = a;}this.alpha = a + i * d;this.tt.style.opacity = this.alpha * 0.01;} else {clearInterval(this.tt.timer);if (d == -1) {this.tt.style.display="none";document.removeEventListener("mousemove", this.moveFunc, false);this.tt = null;}}};this.pos = function (e, inst) {inst.tt.style.top = e.pageY - inst.h + "px";inst.tt.style.left = e.pageX + inst.left + "px";};this.show = function (msg) {if (this.tt == null) {this.tt = document.createElement("div");this.tt.setAttribute("id", this.id);c = document.createElement("div");c.setAttribute("id", this.id + "cont");this.tt.appendChild(c);document.body.appendChild(this.tt);this.tt.style.opacity = 0; this.tt.style.zIndex=100000; var inst = this;this.moveFunc = function (e) {inst.pos(e, inst);};document.addEventListener("mousemove", this.moveFunc, false);}this.tt.style.display = "block";c.innerHTML = msg || this.msg;this.tt.style.width = "auto";if (this.tt.offsetWidth > this.maxw) {this.tt.style.width = this.maxw + "px";}h = parseInt(this.tt.offsetHeight) + this.top;clearInterval(this.tt.timer);var inst = this;this.tt.timer = setInterval(function () {inst.fade(1);}, this.timer);};this.hide = function () {if (this.tt) {clearInterval(this.tt.timer);var inst = this;this.tt.timer = setInterval(function () {inst.fade(-1);}, this.timer);}};} tooltip = new tooltipClass("default txt");';
var newJs = document.createElement('script');
newJs.setAttribute('type', 'text/javascript');
newJs.innerHTML = injectJs;
document.getElementsByTagName('head')[0].appendChild(newJs);
var user = ''; // Current user name/alias
var interval = 1000; // Interval (in ms, >= 100) to re-scan links in the DOM
// Won't re-scan if < 100
// (I might consider using MutationObserver in the future, instead)
function getCurrentUser() {
//
// Return name of user currently logged on IMDb (log on console if failed)
//
var loggedIn = '';
var account = document.getElementById('consumer_user_nav') ||
document.getElementById('nbpersonalize');
if (account) {
var result = account.getElementsByTagName('strong');
if (!result.length) result = account.getElementsByClassName("navCategory");
if (!result.length) result = account.getElementsByClassName("singleLine");
if (!result.length) result = account.getElementsByTagName("p");
if (result)
loggedIn = result[0].textContent.trim();
} else {
var result = document.getElementsByClassName("navbar__user-menu-toggle__name");
if (result)
loggedIn = result[0].textContent.trim();
}
if (!loggedIn)
console.error(document.URL + "\nUser not logged in (or couldn't get user info)"); // responseDetails.responseText
return loggedIn;
}
const WATCHLIST = "watchlist";
const RATINGLIST = "ratings";
const CHECKINS = "checkins";
const TITLES = "Titles";
const PEOPLE = "People";
const IMAGES = "Images";
const VIDEOS = "Videos";
// Lists can be about Titles, People, Images & Videos (no Characters lists anymore?)
// Comment out a list type to disable highlighting for it.
var listTypes = {};
listTypes[TITLES] = true;
listTypes[PEOPLE] = true;
//listTypes[IMAGES] = true; // To-do: highlight images using colored borders?
//listTypes[VIDEOS] = true; // To-do: highlight videos using colored borders?
var listOrderIdx = [];
var myLists = [];
// myLists is our main data structure. It's a list, and here's the description of the 1st entry:
// myLists[0].name == "Your Watchlist" -> Name of the list
// myLists[0].id == "watchlist" -> "id" of the list
// myLists[0].color == "DarkGoldenRod" -> color used to highlight movies in this list
// myLists[0].type == TITLES -> type of entries in this list
// myLists[0].ids["1yjf"].m == "10" -> my rating
// myLists[0].ids["1yjf"].i == "6.6" -> IMDB rating
// "1yjf" = movie number (e.g., "tt0091419") "encoded in base 36"
// To see some color names and codes, visit:
// https://www.w3schools.com/colors/colors_hex.asp
function getMyLists() {
//
// Get all lists (name & id) for current user into myLists array
// and set default colors for them (if not previously defined)
//
// You can customize your lists colors.
// See also the listOrder variable below.
// After any change in the code: save the script, reload the lists page,
// clear the highlight data and refresh the highlight data!
var customColors = [];
customColors["Your Watchlist"] = "DarkGoldenRod";
customColors["Your ratings" ] = "Green";
customColors["Your check-ins"] = "DarkGreen";
customColors["DefaultColor" ] = "DarkCyan";
customColors["DefaultPeople" ] = "DarkMagenta";
customColors["Filmes Netflix Brasil"] = "Red";
// You can set the search order for the highlight color when a title is in multiple lists.
// The script will choose the color of the the first list found in the variable below.
// Uncomment the line below and enter the names of any lists you want to give preference over the others.
var listOrder = ["Your Watchlist", "Your ratings"];
myLists.length = 0; // Clear arrays and insert the two defaults
myLists.push({"name":"Your Watchlist", "id":WATCHLIST, "color":customColors["Your Watchlist"] || "", "ids":{}, "type":TITLES });
myLists.push({"name":"Your ratings", "id":RATINGLIST, "color":customColors["Your ratings"] || "", "ids":{}, "type":TITLES });
myLists.push({"name":"Your check-ins", "id":CHECKINS, "color":customColors["Your check-ins"] || "", "ids":{}, "type":TITLES });
/*
Try to fetch user's custom lists. This code can easily break when IMDb changes its page layout / format...
Data I want to get from HTML: list id, list name, list size, list type, public/private, modified date.
raw HTML = <a class="ipc-metadata-list-summary-item__t" role="button" tabindex="0" aria-disabled="false" href="/list/ls003658871/?ref_=uspf_t_3">Filmes Netflix Brasil</a><ul class="ipc-inline-list ipc-inline-list--show-dividers ipc-inline-list--no-wrap ipc-inline-list--inline ipc-metadata-list-summary-item__tl base" role="presentation"><li role="presentation" class="ipc-inline-list__item"><span class="ipc-metadata-list-summary-item__li" aria-disabled="false">4314 titles</span></li></ul><ul class="ipc-inline-list ipc-inline-list--show-dividers ipc-inline-list--no-wrap ipc-inline-list--inline ipc-metadata-list-summary-item__stl base" role="presentation"><li role="presentation" class="ipc-inline-list__item"><span class="ipc-metadata-list-summary-item__li" aria-disabled="false">Public</span></li><li role="presentation" class="ipc-inline-list__item"><span class="ipc-metadata-list-summary-item__li" aria-disabled="false">Modified Dec 17, 2020</
reduced = "/list/ls003658871/?ref_=uspf_t_3">Filmes Netflix Brasil<…<span…>4314 titles<…<ul…><li…><span…>Public</…<li…><span…>Modified Dec 17, 2020</
*/
const regex = /"\/list\/([^"]+)\/[^>]*>([^<]+)<.{0,500}?>(\d+) (people|person|title|image|video).{0,500}?(Public|Private).{0,500}?Modified (.+?)</g;
const matches = document.body.innerHTML.matchAll(regex);
var numListsFound = 0;
const listTypeMap = {
"person": PEOPLE,
"people": PEOPLE,
"title": TITLES,
"image": IMAGES,
"video": VIDEOS
};
for (const match of matches) {
numListsFound += 1;
const listId = match[1]; // "ls003658871"
const listName = match[2]; // "Filmes Netflix Brasil"
const listSize = match[3]; // "4314"
var listType = match[4]; // "title" (must convert to "Titles")
const listPublic = match[5]; // "Public"
const listDate = match[6]; // "Dec 17, 2020"
if (listType in listTypeMap)
listType = listTypeMap[listType];
//console.log({ listId, listName, listSize, listType, listPublic, listDate });
const colorType = listType == PEOPLE ? "DefaultPeople" : "DefaultColor";
const color = customColors[listName] || customColors[colorType] || "";
myLists.push({"name":listName, "id":listId, "color":color, "ids":{}, "type":listType });
}
if (numListsFound == 0) {
console.error("Error getting lists (or no lists exist)!");
//return false;
}
setListOrder(listOrder);
return true;
}
function loadMyLists() {
//
// Load data for the current user
//
var userData = localStorage.getItem("myMovies-"+user);
if (userData) {
try {
myLists = JSON.parse(userData);
if ("myLists" in myLists) {
listOrderIdx = myLists["listOrder"];
myLists = myLists["myLists" ];
}
return true;
} catch(err) {
alert("Error loading previous data!\n" + err.message);
}
}
return false;
}
function saveMyLists() {
//
// Save data for the current user
//
var userData = {"listOrder": listOrderIdx, "myLists": myLists};
userData = JSON.stringify(userData);
localStorage.setItem("myMovies-"+user, userData);
}
function eraseMyData() {
//
// Erase just the movies and lists information for the user
//
localStorage.removeItem("myMovies-"+user);
for (var i = 0; i < myLists.length; i++)
myLists[i].ids = {};
}
function parseCSV(str) {
// Simple CSV parsing function, by Trevor Dixon:
// https://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data
var arr = [];
var quote = false; // true means we're inside a quoted field
// iterate over each character, keep track of current row and column (of the returned array)
var row, col, c;
for (row = col = c = 0; c < str.length; c++) {
var cc = str[c], nc = str[c+1]; // current character, next character
arr[row] = arr[row] || []; // create a new row if necessary
arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary
// If the current character is a quotation mark, and we're inside a
// quoted field, and the next character is also a quotation mark,
// add a quotation mark to the current column and skip the next character
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
// If it's just one quotation mark, begin/end quoted field
if (cc == '"') { quote = !quote; continue; }
// If it's a comma and we're not in a quoted field, move on to the next column
if (cc == ',' && !quote) { ++col; continue; }
// If it's a newline (CRLF) and we're not in a quoted field, skip the next character
// and move on to the next row and move to column 0 of that new row
if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
// If it's a newline (LF or CR) and we're not in a quoted field,
// move on to the next row and move to column 0 of that new row
if (cc == '\n' && !quote) { ++row; col = 0; continue; }
if (cc == '\r' && !quote) { ++row; col = 0; continue; }
// Otherwise, append the current character to the current column
arr[row][col] += cc;
}
return arr;
}
var downloadedLists = 0;
var listsNotDownloaded = [];
function advanceProgressBar() {
//
// Update progress bar
//
downloadedLists += 1;
var total = myLists.length;
var p = Math.round(downloadedLists*(100/total));
updateProgressBar(p, "Loaded "+downloadedLists+"/"+total);
if (downloadedLists >= total) {
updateProgressBar(0, "");
if (listsNotDownloaded.length > 0) {
var msg = "Done, but could not load list(s):";
listsNotDownloaded.forEach(function(l) { msg += "\n * " + l;} );
msg += "\n\nThis script can only read public lists.";
alert(msg);
} else
alert("OK, we're done!");
}
}
function downloadOK(idx, request, link) {
//
// Process a downloaded list
//
if (request.status != 200) {
console.error("Error "+request.status+" downloading "+link+": " + request.statusText);
} else
if (request.responseText.indexOf("<!DOCTYPE html") >= 0) {
console.error("Received HTML instead of CSV file from "+link);
} else {
var data = parseCSV(request.responseText);
var res, entryCode;
var fields = {};
var type = myLists[idx].type;
for (var i=1; i < data.length; i++) {
if (type == TITLES) {
// ___0___ _____1_____ ____2_____ ___3____ _____4_____ ____5_____ _____6_____ ______7_______ _____8_____ _______9______ ____10___ _____11_____ ___12____ _____13_____ ____14____
// ratings : Const, Your Rating, Date Added, Title, URL, Title Type, IMDb Rating, Runtime (mins), Year, Genres, Num Votes, Release Date, Directors
// others : Position, Const, Created, Modified, Description, Title, URL, Title Type, IMDb Rating, Runtime (mins), Year, Genres, Num Votes, Release Date, Directors
for (var f=0; f < data[0].length; f++)
{ fields[data[0][f]] = data[i][f]; }
var tt = fields["Const"];
var ratingMine = fields["Your Rating"];
var ratingIMDb = fields["IMDb Rating"];
if (typeof tt === "undefined") console.error("Error processing line "+i+" of "+idx);
else if (tt.substr(0,2) != 'tt') console.error("Error getting IMDb const from: "+data[i]);
else {
var ttNum = parseInt(tt.substr(2));
// Encode the movie number with "base 36" to save memory
entryCode = ttNum.toString(36);
myLists[idx].ids[entryCode] = {m:ratingMine, i:ratingIMDb};
}
} else if (type == PEOPLE) {
// ___0___ __1__ ___2___ ___3____ _____4_____ __5__ ____6____ ____7_____
// Position, Const, Created, Modified, Description, Name, Known For, Birth Date
for (var f=0; f < data[0].length; f++)
{ fields[data[0][f]] = data[i][f]; }
var nm = fields["Const"];
//var name = fields["Name"];
if (typeof nm === "undefined") console.error("Error processing line "+i+" of "+idx);
else if (nm.substr(0,2) != 'nm') console.error("Error getting IMDb const from: "+data[i]);
else {
var nmNum = parseInt(nm.substr(2));
// Encode the entry with "base 36" to save memory
entryCode = nmNum.toString(36);
//myLists[idx].ids[entryCode] = {n: name};
myLists[idx].ids[entryCode] = {};
}
} else if (type == IMAGES) {
// Do nothing for now
}
}
// Save data into browser
saveMyLists();
}
advanceProgressBar() ;
// Try to free some memory
delete request.responseText;
}
var createFunction = function( func, p1, p2, p3 ) {
return function() {
func(p1, p2, p3);
};
};
function downloadError(name, request, link) {
//
// Alert user about a download error
//
var msg = "Error downloading your list "+name+":\n"+
"Status: " +request.status + " - " + request.statusText +":\n"+
"Source: " +link +"\n" +
"Headers: " +request.getAllResponseHeaders();
alert(msg);
console.error(msg);
updateProgressBar(0, "");
}
function downloadAsync(name, idx, exportLink) {
var request = new XMLHttpRequest();
request.onload = createFunction(downloadOK, idx, request, exportLink);
request.onerror = createFunction(downloadError, name, request, exportLink);
request.open("GET", exportLink, true);
//request.setRequestHeader("Accept-Encoding","gzip"); // Browser does this already? (I get 'Refused to set unsafe header "Accept-Encoding"')...
request.send();
}
function downloadAsyncWatchlist(name, idx, url) {
var request = new XMLHttpRequest();
request.onload = function() {
var exportLink;
var id = request.responseText.match('<meta property="pageId" content="(ls.+?)"/>') ||
request.responseText.match('"list":{"id":"(ls.+?)"') ||
request.responseText.match('"listId":"(ls.+?)"');
if (id) {
exportLink = document.location.protocol + "//www.imdb.com/list/"+id[1]+"/export";
downloadAsync(name, idx, exportLink);
} else {
console.error("Could not find id of the '"+name+"' list! Try to make it public (you can make it private again right after).");
listsNotDownloaded.push(name);
advanceProgressBar();
}
};
request.onerror = createFunction(downloadError, name, request, url);
request.open("GET", url, true);
request.send();
}
function downloadList(idx) {
//
// Download a list
//
var ur = document.location.pathname.match(/\/(ur\d+)/);
if (ur && ur[1])
ur = ur[1];
else {
alert("Sorry, but I could not find your user ID (required to download your lists). :(");
return;
}
var name = myLists[idx].name;
var id = myLists[idx].id;
// Watchlist & check-ins are not easily available (requires another fetch to find export link)
// http://www.imdb.com/user/ur???????/watchlist/export | shows old HTML format
// http://www.imdb.com/list/export?list_id=watchlist&author_id=ur??????? | 404 error
// http://www.imdb.com/user/ur???????/watchlist | HTML page w/ "export link" at the bottom
if (id == WATCHLIST || id == CHECKINS) {
var url = document.location.protocol + "//www.imdb.com/user/"+ur+"/"+id;
downloadAsyncWatchlist(name, idx, url);
} else {
var exportLink;
if (id == RATINGLIST)
exportLink = document.location.protocol + "//www.imdb.com/user/"+ur+"/"+id+"/export";
else exportLink = document.location.protocol + "//www.imdb.com/list/"+id+"/export";
downloadAsync(name, idx, exportLink);
}
}
function downloadLists() {
//
// Begin to download all user lists at once (asynchronously)
//
downloadedLists = 0;
for (var idx=0; idx < myLists.length; idx++)
downloadList(idx);
// With 10.000 items in 5 lists, the approx. time to download them (on Chrome 29) was:
// - synchronously: 1:50s
// - asynchronously: 30s
// Results might vary - a lot! - depending on number of lists and browser
// Connections per hostname seems to be around 6: http://www.browserscope.org/?category=network&v=top
}
// Really simple progress bar...
var pb;
var pbBox;
var pbTxt;
function createProgressBar(p, msg) {
var top_ = Math.round(window.innerHeight / 2) -15;
var left = Math.round(window.innerWidth / 2) -100;
pbBox = document.createElement('div');
pbBox.style.cssText = "background-color: white; border: 2px solid black; "+
"position: fixed; height: 30px; width: 200px; top: "+top_+"px; left: "+left+"px;";
document.body.appendChild(pbBox);
pb = document.createElement('div');
pb.style.cssText = "background-color: green; border: none; height: 100%; width: "+p+"%;";
pbBox.appendChild(pb);
pbTxt = document.createElement('div');
pbTxt.textContent = msg;
pbTxt.style.cssText = "text-align: center; margin-top: -25px; font-family: verdana,sans-serif;";
pbBox.appendChild(pbTxt);
}
function updateProgressBar(p, msg) {
if (p <= 0) {
pbBox.style.display = "none";
return;
}
pbTxt.textContent = msg;
pb.style.width = p+"%";
}
function setListOrder(listOrder) {
//
// Set color highlight order using lists indices, after variable listOrder (containing lists names).
//
if (typeof listOrder == "undefined")
listOrder = []; // array of lists names
listOrderIdx = []; // array of lists indices
// First add indices set by user in listOrder
for (var j = 0; j < listOrder.length; j++)
for (var i = 0; i < myLists.length; i++)
if (myLists[i].name == listOrder[j]) {
listOrderIdx.push(i);
break;
}
// Add remaining indices
for (var i = 0; i < myLists.length; i++)
if (!listOrderIdx.includes(i))
listOrderIdx.push(i);
}
function movieColor(num, type) {
//
// Receives an IMDb movie code & list type, return the highlight color (if any).
// It will return the color for the first list where the file is found.
// Argument "num": movie number encoded in base 36
//
for (var j = 0; j < listOrderIdx.length; j++) {
var i = listOrderIdx[j];
if (myLists[i].type == type && myLists[i].ids[num])
if (myLists[i].color)
return myLists[i].color;
}
return "";
}
function inLists(num, type) {
//
// Receives an IMDb code and return the names of lists containing it.
// Argument "num" : entry number encoded in base 36
// Argument "type": optional, if set, limits search to a specific type of list
//
var num_l = 0;
var lists = "";
var pos = -1;
var rated = false;
var imdbRating = "";
var header = "";
var movie, name;
for (var i = 0; i < myLists.length; i++) {
if (type && myLists[i].type != type)
continue;
movie = myLists[i].ids[num];
if (movie) {
if (num_l)
lists += "<br>";
name = myLists[i].name;
imdbRating = movie.i;
if (imdbRating && name == "Your ratings") {
name = "Your ratings: " + movie.m + " (IMDb: " + imdbRating + ")";
rated = true;
}
lists += name;
num_l += 1;
}
}
if (imdbRating && !rated)
imdbRating = "IMDb rating: " + imdbRating + "<br>";
else imdbRating = "";
if (num_l == 1)
header = "<b>In your list:</b><br>";
else header = "<b>In "+num_l+" of your lists:</b><br>";
return imdbRating + header + '<div style="margin-left: 15px">' + lists + '</div>';
}
function addTooltip(obj, txt) {
txt = txt.replace(/'/g, '"');
obj.setAttribute("onmouseover", "tooltip.show('"+txt+"');");
obj.setAttribute("onmouseout", "tooltip.hide();");
}
function addTooltipStyle() {
// Tooltips stuff
GM_addStyle("#tt {position:absolute; display:block;} " +
"#ttcont {display:block; padding:2px 12px 3px 7px; margin-left:5px; background:#666; color:#FFF; font:11px/1.5 Verdana, Arial, Helvetica, sans-serif;}");
}
function highlightTitle() {
//
// Highlight title in the current page
//
var entry;
var type;
const isMovie = document.location.href.match(/tt[0]*(\d+)\//);
const isName = document.location.href.match(/nm[0]*(\d+)\//);
if (isMovie) {
entry = isMovie;
type = TITLES;
} else if (isName) {
entry = isName;
type = PEOPLE;
}
if (entry && type in listTypes) {
var num = parseInt(entry[1]).toString(36);
var color = movieColor(num, type);
var lists = inLists(num, type);
if (color) {
var title = document.querySelector("div.title_wrapper > h1");
if (!title)
title = document.querySelector("div[class*=TitleBlock] > h1");
if (!title)
title = document.querySelector("h1 > span");
if (!title)
title = document.querySelector("h1");
if (title) {
title.style.color = color;
addTooltip(title, lists);
}
}
}
}
var lastAnchors;
function highlightLinks() {
//
// Highlight all links in the current page for an IMDb movie page
//
var m, num, color, lists, movie, name, type;
const anchors = document.getElementsByTagName('a');
//if (anchors.length == lastAnchors) return;
for (var i=0; i < anchors.length; i++) {
var a = anchors[i];
if (a.imdbE === undefined) {
a.imdbE = false; // set to "true" when "enhanced" (so we skip it on next pass)
movie = (a.href.indexOf("/tt") >= 0) || (a.href.indexOf("/Title") >= 0) &&
a.href.indexOf("tt_moviemeter_why") == -1;
name = a.href.indexOf("/name/nm") >= 0;
type = false;
if (movie) {
type = TITLES;
//m = a.href.match(/tt[0]*(\d+)\/(.?)/);
m = a.href.match(/tt[0]*(\d+)\/?(.?)/);
if (!m) {
m = a.href.match(/imdb\..{2,3}\/Title\?[0]*(\d+)$/);
// http://www.imdb.com/Title?0266543
if (!m) continue;
}
if (m.length >= 3 && m[2] !== undefined && m[2] != "?" && m[2] !== "")
continue;
} else if (name) {
type = PEOPLE;
// To-do (maybe): skip some links (e.g., quotes, trivia, ...)
// if (a.href.indexOf("/#writer" )) >= 0) continue;
// if (a.href.indexOf("/#director")) >= 0) continue;
// if (a.href.indexOf("/publicity")) >= 0) continue;
m = a.href.match(/nm[0]*(\d+)\/?(.?)/);
}
if (type != false) {
// I "encode" the movie number with "base 36" to save memory
num = parseInt(m[1]).toString(36);
color = movieColor(num, type);
lists = inLists(num, type);
if (color) {
a.style.fontWeight = "bold";
a.style.color = color;
//a.style.fontStyle = "italic";
addTooltip(a, lists);
a.imdbE = true;
}
// Highlight titles & names in search results preview
searchResultTitle = a.querySelector("div[class*=searchResults]");
if (!searchResultTitle)
searchResultTitle = a.querySelector("div[class*=searchResult__constTitle]");
if (searchResultTitle) {
searchResultTitle.style.color = color;
}
}
}
}
lastAnchors = anchors.length;
}
function refreshMovieData() {
alert(myName+"\n\n"+user+", I'll get some info from IMDb to be able to highlight your movies,\nplease click [OK] and wait a bit...");
eraseMyData();
createProgressBar(0, "Loading 1/"+myLists.length+"...");
downloadLists();
}
var btn1; // refresh
var btn2; // clear
var btn4; // help
function btnRefresh() {
refreshMovieData();
}
function btnClear() {
eraseMyData();
alert(myName+"\n\nDone! Information cleared, so highlighting is now disabled.");
window.location.reload();
}
function btnHelp () {
alert(myName+"\n\nThis is a user script that:\n"+
" • highlights links for entries in your lists (e.g., movies, series & people)\n"+
" • shows in which of your lists an entry is (in a tooltip)\n"+
"\nIn order to highlight the entries "+
"in all IMDb pages as fast as possible, we need to download "+
"the data from your lists into your browser. Unfortunately " +
"this can be slow, so it is not done automatically. I suggest "+
"you to update this information at most once a day.\n\n" +
"[Refresh highlight data] updates the data in your browser.\n" +
"[Clear highlight data] disables color highlighting.\n" +
"\nFor more information and updates, visit " + myHome
);
}
function addBtn(div, func, txt, help) {
var b = document.createElement('button');
b.className = "ipc-btn ipc-btn--core-accent1 ipc-btn--theme-baseAlt"; //"btn";
b.style.cssText = "margin-right: 10px; margin-bottom: 10px; font-size: 11px;";
b.textContent = txt;
b.title = help;
b.addEventListener('click', func, false);
div.appendChild(b);
return b;
}
function addButtons() {
const h1 = document.body.getElementsByTagName("h1");
if (h1) {
var div = document.createElement('div');
div.className = "aux-content-widget-2";
div.style.cssText = "margin-top: 10px;";
btn1 = addBtn(div, btnRefresh, "Refresh highlight data", "Reload information from your lists - might take a few seconds");
btn2 = addBtn(div, btnClear, "Clear highlight data", "Disable color highlighting of your lists");
btn4 = addBtn(div, btnHelp, "What's this?", "Click for help on these buttons");
h1[0].appendChild(div);
} else console.error('Could not find "<h1>Your Lists</h1>" to insert buttons!');
}
//-------- "main" --------
var we_are_in_the_lists_page = false;
if (document.location.href.match(/\.imdb\..{2,3}\/user\/[^\/]+\/lists/)) {
we_are_in_the_lists_page = true;
getMyLists();
}
// Find current logged in user, or quit script
user = getCurrentUser();
if (!user) return; // FIX-ME: to support external sites: set/get LAST user to/from browser storage
// Allow user to manually update his/her lists
if (we_are_in_the_lists_page) {
addButtons();
return; // Nothing else to do on the lists page - goodbye!
}
// Load lists data for this user from localStorage
loadMyLists();
// highlight movie links
if (myLists.length) {
addTooltipStyle();
highlightTitle();
highlightLinks();
if (interval >= 100)
setInterval(highlightLinks, interval);
}
})();
// Test URLs:
// http://www.imdb.com/mymovies/list
// http://www.imdb.com/title/tt0110912/movieconnections/
// http://www.imdb.com/chart/top
// https://www.imdb.com/chart/top
// http://www.imdb.com/genre/sci_fi
// http://www.imdb.com/search/title?genres=sci_fi&title_type=feature&num_votes=1000,&sort=user_rating,desc
// http://www.imdb.com/event/ev0000003/2011
// http://www.imdb.com/year/2004
// Over the "instantaneous results" below the search box
// Funny... Shark Tale on the page above points to http://www.imdb.com/title/tt0384531/,
// but when opened it redirects to ............... http://www.imdb.com/title/tt0307453/
// Titles producing invalid CSV files:
// http://www.imdb.com/title/tt0095675/
// http://www.imdb.com/title/tt0365748/
// Test people vs movies:
// https://www.imdb.com/title/tt0000697/
// https://www.imdb.com/name/nm0000697/
// https://www.imdb.com/title/tt0053291/