// Enhance titles - Netflix
//
// Loads lists of movies from a local list and an IMDb account and uses
// them to highlight or hide titles on Netflix.
//
// https://greasyfork.org/scripts/390631-enhance-titles-netflix
// Copyright (C) 2019, Guido Villa
// IMDb list management is taken from IMDb 'My Movies' enhancer:
// Copyright (C) 2008-2018, Ricardo Mendonça Ferreira (ric@mpcnet.com.br)
// Released under the GPL license - http://www.gnu.org/copyleft/gpl.html
//
// For instructions on user scripts, see:
// https://greasyfork.org/help/installing-user-scripts
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name Enhance titles - Netflix
// @description Emphasize or hide titles on Netflix according to IMDb and local lists
// @version 1.5
// @author guidovilla
// @date 20.10.2019
// @copyright 2019, Guido Villa (https://greasyfork.org/users/373199-guido-villa)
// @license GPL-3.0-or-later
// @homepageURL https://greasyfork.org/scripts/390631-enhance-titles-netflix
// @supportURL https://gitlab.com/gv-browser/userscripts/issues
// @contributionURL https://tinyurl.com/gv-donate-7e
// @attribution Ricardo Mendonça Ferreira (https://openuserjs.org/users/AltoRetrato)
// @attribution Trevor Dixon (https://stackoverflow.com/users/711902/trevor-dixon)
//
// @namespace https://greasyfork.org/users/373199-guido-villa
//
// @match https://www.netflix.com/*
// @match https://www.imdb.com/user/*/lists*
// @exclude https://www.netflix.com/watch*
//
// @require https://greasyfork.org/scripts/390248-entrylist/code/EntryList.js
// @require https://greasyfork.org/scripts/391236-progressbar/code/ProgressBar.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_notification
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @downloadURL none
// ==/UserScript==
//
// --------------------------------------------------------------------
//
// To-do (priority: [H]igh, [M]edium, [L]ow):
// - [H] List/color configuration is hard-coded -> make configurable
// - [H] Not all IMDb movies are recognized because matching is done by title
// (maybe use https://greasyfork.org/en/scripts/390115-imdb-utility-library-api)
// - [M] Move GMprom_xhR and parseCSV to utility library (with some from EntryList)
// - [M] Move IMDb list function to utility library
// - [M] Optimize imdb list parsing
// - [M] Show name in tooltip? Maybe not needed if above is solved
// - [M] Make triangles more visible
// - [M] Show in tooltip all lists where title is present?
// - [M] GMprom_xhR: remove workaround responseXML2 and have responseXML work
// - [M] Remove commented code
// - [M] Lots of clean-up
// - [M] Add comments
// - [M] Delay autopreview for hidden movies?
// - [L] Re-enable download of ratings and checkins
// - [L] No link between IMDb user and Netflix user, implement getSourceUserFromTargetUser
// - [L] hide selective titles?
//
// Changelog:
// ----------
// 2019.10.20 [1.5] Refactor using EntryList library (first version)
// 2019.09.30 [1.4] First public version, correct @namespace and other headers
// 2019.08.28 [1.3] Make the list more visible (top right triangle instead of border, with tooltip)
// Fix unhide function (bug added in 1.2)
// Add priority in todo list
// 2019.07.06 [1.2] Fix working in pages without rows (i.e. search page)
// Fix opacity not applied in some cases/pages
// 2019.06.20 [1.1] Load My List from My List page
// 2019.06.01 [1.0] Hide "My List" titles outside "My List" (row and page) and "Continue watching"
// Fix user name detection
// Gets data both from locally hidden movies and from IMDb lists
// 2019.03.30 [0.1] First test version, private use only
//
/* jshint -W008 */
/* global EL: readonly, ProgressBar: readonly */
(function() {
'use strict';
/* BEGIN CONTEXT DEFINITION */
var netflix = EL.newContext('Netflix');
var imdb = EL.newContext('IMDb');
// other variables
// TODO ci deve essere un modo migliore di questo
var LIST_HIDE = 'localHide';
var LIST_NF_MY = 'nfMyList';
var LIST_NO = 'no'; // myLists[neededLists.no] XXX rimuovere poi questi commenti
var LIST_SEEN = 'Visti'; // myLists[neededLists.visti]
var LIST_WATCH = 'Your Watchlist'; // myLists[neededLists.watch]
var LIST_TBD = 'tbd'; // myLists[neededLists.tbd]
var IMDB_LIST_PAGE = 1; // any context-wide unique, non-falsy value is good
var NF_LIST_PAGE = 2; // any context-wide unique, non-falsy value is good
// Netflix
netflix.getUser = function() {
var user = document.querySelector('div.account-menu-item div.account-dropdown-button > a');
if (user) user = user.getAttribute("aria-label");
if (user) user = user.match(/^(.+) - Account & Settings$/);
if (user && user.length >= 2) user = user[1];
return user;
};
netflix.isEntryPage = function() {
return !document.location.href.match(/www\.imdb\.com\//);
};
netflix.getPageEntries = function() {
return document.querySelectorAll('div.title-card');
};
netflix.modifyEntry = function(entry) {
var b = document.createElement('a');
b.className = "nf-svg-button simpleround";
b.textContent = 'H';
b.title = 'Hide/show this title';
var d = document.createElement('div');
d.className = "nf-svg-button-wrapper";
d.style.cssText = 'bottom: 0; position: absolute; z-index: 10';
d.appendChild(b);
EL.addToggleEventOnClick(b, 2, LIST_HIDE, 'H');
entry.appendChild(d);
};
netflix.getIdFromEntry = function(entry) {
var a = entry.querySelector('a[href^="/watch/"]');
var id = null;
if (a) {
id = a.href.match(/\/watch\/([^/?&]+)[/?&]/);
if (id && id.length >= 2) id = id[1];
}
if (!id) return null;
var title = entry.querySelector(".fallback-text");
if (title) title = title.innerText;
if (!title) console.error('Cannot find title for entry with id ' + id + ' on URL ' + document.URL, entry);
return { 'id': id, 'name': (title || id) };
};
netflix.determineType = function(lists, _I_tt, entry) {
var type = null;
if (entry.classList.contains('is-disliked')) type = 'D';
else if (lists[EL.ln(imdb, LIST_WATCH)]) type = 'W';
else if (lists[EL.ln(imdb, LIST_TBD)]) type = 'T';
else if (lists[EL.ln(imdb, LIST_SEEN)]) type = 'S';
else if (lists[EL.ln(imdb, LIST_NO)]) type = 'N';
else if (lists[EL.ln(netflix, LIST_HIDE)]) type = 'H';
if (lists[EL.ln(netflix, LIST_NF_MY)] && (!type || type === 'W' || type === 'T') && this.pageType != NF_LIST_PAGE) {
var row = entry.closest('div.lolomoRow');
if (!row || ['queue', 'continueWatching'].indexOf(row.dataset.listContext) == -1) type = 'M';
}
return type;
};
var hideTypes = {
"H": { "name": 'Hidden', "colour": 'white' },
"D": { "name": 'Disliked', "colour": 'black' },
"W": { "name": 'Watchlist', "colour": 'darkgoldenrod', "visible": true },
"T": { "name": 'TBD', "colour": 'Maroon', "visible": true },
"S": { "name": 'Watched', "colour": 'seagreen' },
"N": { "name": 'NO', "colour": 'darkgrey' },
"M": { "name": 'My list', "colour": 'yellow' },
"MISSING": { "name": 'Hide type not known', "colour": 'red' },
};
var TRIANGLE_STYLE_NAME = 'entrylist-netflix-triangle';
var TRIANGLE_STYLE = '.' + TRIANGLE_STYLE_NAME + '{'
+ 'border-right: 20px solid;'
+ 'border-bottom: 20px solid transparent;'
+ 'height: 0;'
+ 'width: 0;'
+ 'position: absolute;'
+ 'top: 0;'
+ 'right: 0;'
+ 'z-index: 2;'
+ '}';
netflix.processItem = function(entry, _I_tt, processingType) {
if (!processingType || !hideTypes[processingType]) processingType = 'MISSING';
var triangle = document.createElement('div');
triangle.className = 'NHT-triangle ' + TRIANGLE_STYLE_NAME;
triangle.style.borderRightColor = hideTypes[processingType].colour;
triangle.title = hideTypes[processingType].name;
entry.parentNode.appendChild(triangle);
if (!hideTypes[processingType].visible) entry.parentNode.style.opacity = .1;
/*
var parent = entry.parentNode;
parent.parentNode.style.width = '5%';
var field = parent.querySelector('fieldset#hideTitle' + tt.id);
if (!field) {
field = document.createElement('fieldset');
field.id = 'hideTitle' + tt.id;
field.style.border = 0;
field.appendChild(document.createTextNode(tt.name));
parent.appendChild(field);
} else {
field.style.display = 'block';
}
*/
};
netflix.unProcessItem = function(entry, _I_tt, _I_processingType) {
entry.parentNode.style.opacity = 1;
var triangle = entry.parentNode.querySelector('.NHT-triangle');
if (triangle) triangle.parentNode.removeChild(triangle);
/*
entry.parentNode.parentNode.style.width = null;
entry.parentNode.querySelector('fieldset#hideTitle' + tt.id).style.display = 'none';
*/
};
netflix.getPageType = function() {
return ( document.location.href == 'https://www.netflix.com/browse/my-list' && NF_LIST_PAGE );
};
// add buttons on the Netflix "My List" page
netflix.processPage = function(_I_pageType, _I_isEntryPage) {
// no need to check pageType: as of now there is only one
var main = document.querySelector('div.mainView');
if (!main) {
console.error('Could not find "main
" to insert buttons');
return;
}
var div = document.createElement('div');
var btnStyle = 'margin-left: 20px; margin-bottom: 20px; font-size: 13px; padding: .5em; background: 0 0; color: grey; border: soli 1px grey;';
addBtn(div, btnNFMyListRefresh, "Load My List data", "Reload information from 'My List'", btnStyle);
addBtn(div, btnNFMyListClear, "Clear My List data", "Empty the data from 'My List'", btnStyle);
main.appendChild(div);
};
// IMDb
imdb.getUser = function() {
var account = document.getElementById('nbusername');
if (!account) return;
var user = account.textContent.trim();
var ur = account.href;
if (ur) ur = ur.match(/\.imdb\..{2,3}\/.*\/(ur[0-9]+)/);
if (ur && ur[1]) ur = ur[1];
else console.error('Cannot retrieve the ur id for user:', user);
return { 'name': user, 'payload': ur };
};
imdb.getPageType = function() {
return ( document.location.href.match(/\.imdb\..{2,3}\/user\/[^/]+\/lists/) && IMDB_LIST_PAGE );
};
// add buttons on the IMDb lists page
imdb.processPage = function(_I_pageType, _I_isEntryPage) {
// no need to check pageType: as of now there is only one
var main = document.getElementById("main");
var h1 = ( main && main.getElementsByTagName("h1") );
if (!h1 || !h1[0]) {
console.error('Could not find element to insert buttons.');
return;
}
var div = document.createElement('div');
div.className = "aux-content-widget-2";
div.style.cssText = "margin-top: 10px;";
addBtn(div, btnIMDbListRefresh, "NF - Refresh highlight data", "Reload information from lists - might take a few seconds");
addBtn(div, btnIMDbListClear, "NF - Clear highlight data", "Remove list data");
h1[0].appendChild(div);
};
// lookup IMDb movies by name
imdb.inList = function(tt, list) {
return !!(list[tt.name]);
};
/* END CONTEXT DEFINITION */
/* BEGIN COMMON FUNCTIONS */
function addBtn(div, func, txt, help, style) {
var b = document.createElement('button');
b.className = "btn";
if (!style) style = "margin-right: 10px; font-size: 11px;";
b.style.cssText = style;
b.textContent = txt;
b.title = help;
b.addEventListener('click', func, false);
div.appendChild(b);
return b;
}
/* END COMMON FUNCTIONS */
/* BEGIN NETFLIX FUNCTIONS */
function btnNFMyListClear() {
NFMyListClear();
GM_notification({'text': "Information from 'My List' cleared.", 'title': EL.title + ' - Clear Netflix My List', 'timeout': 0});
}
function btnNFMyListRefresh() {
var txt;
if (NFMyListRefresh()) txt = "'My List' loaded.";
else txt = "An error occurred. It was not possible to load 'My List' data.";
GM_notification({'text': txt, 'title': EL.title + ' - Load Netflix My List', 'timeout': 0});
}
function NFMyListClear() {
EL.deleteList(netflix, LIST_NF_MY);
delete netflix.allLists[LIST_NF_MY];
}
function NFMyListRefresh() {
NFMyListClear();
var gallery = document.querySelector('div.mainView div.gallery');
var cards = ( gallery && gallery.querySelectorAll('div.title-card') );
if (!cards) return false;
var list = {};
var entry, tt;
for (var i = 0; i < cards.length; i++) {
entry = cards[i];
tt = netflix.getIdFromEntry(entry);
list[tt.id] = tt.name;
}
EL.saveList(netflix, list, LIST_NF_MY);
return true;
}
/* END NETFLIX FUNCTIONS */
/* BEGIN IMDB FUNCTIONS */
function btnIMDbListClear() {
IMDbListClear();
GM_notification({'text': "Information from IMDb cleared.", 'title': EL.title + ' - Clear IMDb lists', 'timeout': 0});
}
function btnIMDbListRefresh() {
GM_notification({
'text': 'Click to start loading the IMDb lists. This may take several seconds',
'title': EL.title + ' - Load IMDb lists',
'timeout': 0,
'onclick': IMDbListRefresh,
});
}
function IMDbListClear() {
EL.deleteAllLists(imdb);
delete imdb.allLists;
}
function IMDbListRefresh() {
var pb = new ProgressBar(-1, 'Loading {#}/{$}...');
var closeMsg = 'An error occurred. It was not possible to download the IMDb lists.';
getIMDbLists()
.then(function(lists) { pb.update(0, null, lists.length); return lists; })
.then(function(lists) { return IMDbListDownload(lists, pb); } )
.then(function(outcomes) {
var msg = outcomes.reduce(function(msg, outcome) {
if (outcome.status === 'rejected') {
msg.txt += "\n * " + outcome.reason;
msg.numKO++;
}
return msg;
}, { 'txt': '', 'numKO': 0 });
if (msg.numKO === 0) {
closeMsg = 'Loading complete!';
} else if (msg.numKO < outcomes.length) {
closeMsg = 'Done, but with errors:' + msg.txt;
console.error('Errors in list download:' + msg.txt);
} else {
throw 'Error - It was not possible to download the IMDb lists:' + msg.txt;
}
})
.catch(function(err) { console.error(err); closeMsg = err; })
.finally(function() {
GM_notification({
'text': closeMsg,
'title': EL.title + ' - Load IMDb lists',
'highlight': true,
'timeout': 5,
'ondone': pb.close,
});
});
}
// Return a Promise to download and save all lists
function IMDbListDownload(lists, pb) {
IMDbListClear();
var allDnd = lists.map(function(list) {
return downloadList(list.id, list.type)
.then(function(listData) { EL.saveList(imdb, listData, list.name); })
.then(pb.advance)
.catch(function(error) { pb.advance(); throw "list '" + list.name + "' - " + error; });
});
return Promise.allSettled(allDnd);
}
var WATCHLIST = "watchlist";
var RATINGLIST = "ratings";
var CHECKINS = "checkins";
var TITLES = "Titles";
var PEOPLE = "People";
var IMAGES = "Images";
// Return a Promise to get all lists (name, id, type) for current user
function getIMDbLists() {
return findIMDbLists().then(getIMDbListFromPage);
}
function findIMDbLists() {
if (document.location.href.match(/\.imdb\..{2,3}\/user\/[^/]+\/lists/)) {
return Promise.resolve(document);
} else {
var url = 'https://www.imdb.com/user/' + imdb.userPayload + '/lists';
return GMprom_xhR('GET', url, 'Get IMDb list page', { 'responseType': 'document' })
.then(function(response) { return response.responseXML2; });
}
}
function getIMDbListFromPage(document) {
var listElements = document.getElementsByClassName('user-list');
if (!listElements) throw "Error getting IMDb lists from page";
var lists = Array.prototype.map.call(listElements, function(listElem) {
var tmp = listElem.getElementsByClassName("list-name");
var name;
if (!tmp || !tmp[0]) {
console.error("Error reading name of list with id " + listElem.id);
name = listElem.id;
} else {
name = tmp[0].text;
}
return {"name": name, "id": listElem.id, 'type': listElem.dataset.listType };
});
lists.push({"name": LIST_WATCH, "id": WATCHLIST, 'type': TITLES });
return lists;
}
// Return a promise to download a list
function downloadList(id, type) {
var getUrl;
if (id == WATCHLIST || id == CHECKINS) {
// Watchlist & check-ins are not easily available (requires another fetch to find export link)
// http://www.imdb.com/user/ur???????/watchlist | HTML page w/ "export link" at the bottom
var url = 'https://www.imdb.com/user/' + imdb.userPayload + '/' + id;
getUrl = GMprom_xhR('GET', url, "Get list page", { 'responseType': 'document' })
.then(function(response) {
var exportLink;
var lsId = response.responseXML2.querySelector('meta[property="pageId"]');
if (lsId) lsId = lsId.content;
if (lsId) exportLink = "https://www.imdb.com/list/" + lsId + "/export";
else {
exportLink = response.responseXML2.getElementsByClassName('export');
if (exportLink) exportLink = exportLink[0];
if (exportLink) exportLink = exportLink.getElementsByTagName('a');
if (exportLink) exportLink = exportLink[0];
if (exportLink) exportLink = exportLink.href;
if (!exportLink) throw 'Cannot get list id';
}
return exportLink;
});
} else if (id == RATINGLIST) {
getUrl = Promise.resolve("https://www.imdb.com/user/" + imdb.userPayload + "/" + id + "/export");
} else {
getUrl = Promise.resolve("https://www.imdb.com/list/" + id + "/export");
}
return getUrl
.then(function(url) { return GMprom_xhR('GET', url, "download"); })
.then(function(response) { return parseList(response, type); });
}
/* END IMDB FUNCTIONS */
//-------- "main" --------
GM_addStyle(TRIANGLE_STYLE);
EL.init(netflix);
EL.addSource(imdb);
EL.startup();
//***// var RATINGLIST = "ratings";
//***// var CHECKINS = "checkins";
//***//
//***// // Lists can be about Titles, People & Images (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?
//***//
//***// var listOrderIdx = [];
//***//
//***// var myLists = [];
//***// var neededLists = {}; //GUIDO NF
//***//
//***//
//***//
//***// var myName = 'Netflix hide titles'; // Name & version of this script
//***// var user = ''; // Current user name/alias
//***// var IMDbUser = '';
//***// 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)
//***//
//***//
//***// var myLocalList = {};
//***// var myNetflixList = {};
//***//
//***// function loadMyLocalList() {
//***// //
//***// // Load data for the current user
//***// //
//***// var userData = GM_getValue("NetflixHideList-"+user, null);
//***// if (userData) {
//***// try {
//***// myLocalList = JSON.parse(userData);
//***// return true;
//***// } catch(err) {
//***// alert("Error loading Netflix local data!\n" + err.message);
//***// }
//***// }
//***// }
//***//
//***// function loadMyNetflixList() {
//***// //
//***// // Load data for the current user
//***// //
//***// var userData = GM_getValue("NetflixMyList-"+user, null);
//***// if (userData) {
//***// try {
//***// myNetflixList = JSON.parse(userData);
//***// return true;
//***// } catch(err) {
//***// alert("Error loading Netflix My List data!\n" + err.message);
//***// }
//***// }
//***//
//***// return false;
//***// }
//***//
//***// function getMyIMDbLists() {
//***// //
//***// // 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";
//***////GUIDO customColors["DefaultColor" ] = "DarkCyan";
//***// customColors["DefaultColor" ] = "Maroon";
//***// customColors["DefaultPeople" ] = "DarkMagenta";
//***////GUIDO customColors["Filmes Netflix Brasil"] = "Red";
//***// customColors["Visti"] = "seagreen";
//***// customColors["Parzialmente visti"] = "yellowgreen";
//***// customColors["no"] = "darkgrey";
//***//
//***// // 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 });
//***// var lists = document.getElementsByClassName('user-list');
//***// if (!lists || lists.length < 1) {
//***// console.error("Error getting lists (or no lists exist)!");
//***// return false;
//***// }
//***// for (var i = 0; i < lists.length; i++) {
//***// var listType = lists[i].getAttribute("data-list-type");
//***// if (listType in listTypes) {
//***// var tmp = lists[i].getElementsByClassName("list-name");
//***// if (!tmp) {
//***// console.error("Error reading information from list #"+i);
//***// continue;
//***// }
//***// tmp = tmp[0]; //
Filmes Netflix Brasil
//***// var name = tmp.text;
//***// var id = tmp.href.match(/\/list\/([^\/\?]+)\/?/)[1];
//***// var colorType = listType == PEOPLE ? "DefaultPeople" : "DefaultColor";
//***// var color = customColors[name] || customColors[colorType] || "";
//***// myLists.push({"name":name, "id":id, "color":color, "ids":{}, "type":listType });
//***// }
//***// }
//***// setListOrder(listOrder);
//***// return true;
//***// }
//***//
//***// function loadMyIMDbLists() {
//***// //
//***// // Load data for the current user
//***// //
//***//// var userData = localStorage.getItem("myMovies-"+user); // GUIDO NF
//***// var userData = GM_getValue("myIMDbMovies-"+user, null); // GUIDO NF
//***// if (userData) {
//***// try {
//***// myLists = JSON.parse(userData);
//***// if ("myLists" in myLists) {
//***// listOrderIdx = myLists["listOrder"];
//***// myLists = myLists["myLists" ];
//***//
//***// // GUIDO NF
//***// for (var i = 0; i < myLists.length; i++) {
//***// if (myLists[i].type != TITLES) continue;
//***// switch (myLists[i].name) {
//***// case 'no': neededLists.no = i; break;
//***// case 'Visti': neededLists.visti = i; break;
//***// case 'Your Watchlist': neededLists.watch = i; break;
//***// case 'tbd': neededLists.tbd = i; break;
//***// }
//***// }
//***// // FINE GUIDO NF
//***// }
//***// return true;
//***// } catch(err) {
//***// alert("Error loading previous data!\n" + err.message);
//***// }
//***// }
//***// return false;
//***// }
//***//
//***// function saveMyLocalList() {
//***// //
//***// // Save data for the current user
//***// //
//***// var userData = JSON.stringify(myLocalList);
//***// GM_setValue("NetflixHideList-"+user, userData);
//***// }
//***//
//***// function saveMyIMDbLists() {
//***// //
//***// // Save data for the current user
//***// //
//***// var userData = JSON.stringify(myLocalList);
//***// GM_setValue("NetflixHideList-"+user, userData);
//***//
//***// userData = {"listOrder": listOrderIdx, "myLists": myLists};
//***// userData = JSON.stringify(userData);
//***// GM_setValue("myIMDbMovies-"+user, userData);
//***// }
//***//
//***// function toggleTitle(evt) {
//***// // get title id
//***// var div = evt.target.parentNode.parentNode;
//***// var tt = getIdFromDiv(div);
//***//
//***// // check if item is in list
//***// if (myLocalList[tt.id]) {
//***// delete myLocalList[tt.id];
//***// showItem(div, tt.id);
//***// } else {
//***// myLocalList[tt.id] = tt.name;
//***// hideItem(div, tt);
//***// }
//***// saveMyLocalList();
//***// }
//***//
//***//
//***//
//***//
//***//
//***///* FROM IMDB MY MOVIES ENHANCER */
//***// var downloadedLists = 0;
//***// var listsNotDownloaded = [];
//***//
//***//
//***// function advanceProgressBar() {
//***// //
//***// // Update progress bar
//***// //
//***// downloadedLists += 1;
//***// var total = myLists.length;
//***// var p = Math.round(downloadedLists*(100/total));
//***// pb.advance();
//***// if (downloadedLists >= total) {
//***// pb.close();
//***// 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!");
//***// }
//***// }
// Process a downloaded list
function parseList(response, type) {
if (response.responseText.startsWith("');
//***// if (lsId && lsId.length > 1)
//***// exportLink = document.location.protocol + "//www.imdb.com/list/"+lsId[1]+"/export";
//***// else {
//***// lsId = request.responseText.match('"list":{"id":"(ls.+?)"');
//***// if (lsId && lsId.length > 1)
//***// exportLink = document.location.protocol + "//www.imdb.com/list/"+lsId[1]+"/export";
//***// }
//***// if (exportLink)
//***// downloadAsync(name, id, 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 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 ii = 0; ii < myLists.length; ii++)
//***// if (!listOrderIdx.includes(ii))
//***// listOrderIdx.push(ii);
//***// }
//***//
//***//
//***//
//***///* END */
//***//
//***//
//***// //IMDb
//***// var btn1; // refresh
//***// var btn2; // clear
//***// var btn4; // help
//***// //Netflix
//***// var btn8; // refresh
//***// var btn16; // clear
//***//
//***// 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"
//***// );
//***// }
//***//
//***//
//***//
//***// //-------- "main" --------
//***// var we_are_in_a_title_page = false;
//***// var we_are_in_the_imdb_list_page = false;
//***// var we_are_in_the_netflix_list_page = false;
//***//
//***// if (document.location.href.match(/\.netflix\..{2,3}\//)) {
//***// we_are_in_a_title_page = true;
//***// }
//***//
//***// if (document.location.href == 'https://www.netflix.com/browse/my-list') {
//***// we_are_in_the_netflix_list_page = true;
//***// }
//***// if (document.location.href.match(/\.imdb\..{2,3}\/user\/[^\/]+\/lists/)) {
//***// we_are_in_the_imdb_list_page = true;
//***// }
//***//
//***//
//***// // Find current logged in user, or quit script
//***// user = getCurrentNetflixUser();
//***// if (!user) return;
//***//
//***//
//***// // Allow user to manually update his/her lists
//***// if (we_are_in_the_imdb_list_page) {
//***// getMyIMDbLists();
//***// // Find current logged in user, or quit script
//***// IMDbUser = getCurrentIMDbUser();
//***// if (!IMDbUser) return; // FIX-ME: to support external sites: set/get LAST user to/from browser storage
//***//
//***// addIMDbButtons();
//***// return; // Nothing else to do on the lists page - goodbye!
//***// }
//***// if (we_are_in_the_netflix_list_page) {
//***// addNetflixButtons();
//***// }
//***//
//***// if (we_are_in_a_title_page) {
//***// // Load lists data for this user from localStorage
//***// loadMyLocalList();
//***// loadMyNetflixList();
//***// loadMyIMDbLists();
//***// }
//***//
//***//
//***//
//***//// THIS IS THE NEW PART
//***//
//***//
//***// function hideTitleCards() {
//***//// console.log('waitnlp',we_are_in_the_netflix_list_page);
//***// //
//***// // Highlight all title cards in the current Netflix page
//***// //
//***//
//***// var num, color, lists, movie;
//***// var anchors = document.querySelectorAll('div.title-card');
//***//
//***// for (var i=0; i < anchors.length; i++) {
//***// var a = anchors[i];
//***// if (!a.GVhide) {
//***// addHideBtn(a, toggleTitle, 'H', 'Hide/show this title');
//***//
//***// var tt = getIdFromDiv(a);
//***// var title, movieTitle;
//***// var hideType = null;
//***// if (a.className.indexOf('is-disliked') != -1) hideType = 'D';
//***// else {
//***// movie = a.querySelector(".fallback-text");
//***// if (movie) movieTitle = movie.innerText;
//***//
//***// if (movieTitle) {
//***// num = movieTitle;
//***// //lists = inLists(num, TITLES);
//***// if (myLists[neededLists.watch].ids[num]) {
//***// hideType = 'W';
//***// } else if (myLists[neededLists.tbd].ids[num]) {
//***// hideType = 'T';
//***// } else if (myLists[neededLists.visti].ids[num]) {
//***// hideType = 'S';
//***// } else if (myLists[neededLists.no].ids[num]) {
//***// hideType = 'N';
//***// }
//***// }
//***// }
//***// if (!hideType && (title = myLocalList[tt.id])) hideType = 'H';
//***// if ((!hideType || hideType == 'W') && !we_are_in_the_netflix_list_page && (title = myNetflixList[tt.id])) {
//***//// console.log('ht',hideType,'tt.id',tt.id,'mt',movieTitle);
//***// var row = a.closest('div.lolomoRow');
//***// if (!row || (row.dataset.listContext != 'queue' && row.dataset.listContext != 'continueWatching')) hideType = 'M';
//***//// console.log('rownull',!row,'lt',row.dataset.listContext);
//***// }
//***//
//***// if (hideType) hideItem(a, tt.id, title, hideType);
//***// a.GVhide = true; // set to "true" when "enhanced" (so we skip it on next pass)
//***// }
//***// }
//***// }
//***//
//***//
//***//
//***// // start the hiding title function
//***//// if (myLists.length) {
//***// hideTitleCards();
//***// if (interval >= 100) setInterval(hideTitleCards, interval);
//***//// }
// handle download error in a Promise-enhanced GM_xmlhttpRequest
function xhrError(rejectFunc, response, method, url, purpose, reason) {
var m = purpose + ' - HTTP ' + method + ' error' + (reason ? ' (' + reason + ')' : '') + ': '
+ response.status + (response.statusText ? " - " + response.statusText : '');
console.error(m, 'URL: ' + url, 'Response:', response);
rejectFunc(m);
}
function xhrErrorFunc(rejectFunc, method, url, purpose, reason) {
return function(resp) { xhrError(rejectFunc, resp, method, url, purpose, reason); };
}
function GMprom_xhR(method, url, purpose, opts) {
return new Promise(function(resolve, reject) {
var details = opts || {};
details.method = method;
details.url = url;
details.onload = function(response) {
if (response.status !== 200) xhrError(reject, response, method, url, purpose);
// else resolve(response);
else {
if (details.responseType === 'document') {
try {
const doc = document.implementation.createHTMLDocument().documentElement;
doc.innerHTML = response.responseText;
response.responseXML2 = doc;
} catch(e) {
xhrError(reject, response, method, url, purpose, e);
}
}
resolve(response);
}
};
details.onabort = xhrErrorFunc(reject, method, url, purpose, 'abort');
details.onerror = xhrErrorFunc(reject, method, url, purpose, 'error');
details.ontimeout = xhrErrorFunc(reject, method, url, purpose, 'timeout');
if (typeof details.synchronous === 'undefined') details.synchronous = false;
GM_xmlhttpRequest(details);
});
}
function parseCSV(str) {
// Simple CSV parsing function, by Trevor Dixon:
// https://stackoverflow.com/a/14991797
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;
}
}());