TitleList

Common functions for working on lists of titles

目前为 2019-09-20 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/390248/734489/TitleList.js

  1. //{
  2. // Common functions for working on lists of titles, loading them, highlighting
  3. // titles based on these lists.
  4. //
  5. // Copyright (c) 2019, Guido Villa
  6. // Most of the code is taken from IMDb 'My Movies' enhancer:
  7. // Copyright (c) 2008-2018, Ricardo Mendonça Ferreira (ric@mpcnet.com.br)
  8. // Released under the GPL license - http://www.gnu.org/copyleft/gpl.html
  9. //
  10. // --------------------------------------------------------------------
  11. //
  12. // ==UserScript==
  13. // @name TitleList
  14. // @description Common functions for working on lists of titles
  15. // @namespace https://greasyfork.org/en/scripts/390248-titlelist
  16. // @updateURL about:blank
  17. // @homepageURL https://greasyfork.org/en/scripts/390248-titlelist
  18. // @copyright 2019, Guido Villa
  19. // @license GPL-3.0-or-later
  20. // @oujs:author Guido
  21. // @date 18.09.2019
  22. // @version 0.1
  23. // ==/UserScript==
  24. //
  25. // To-do (priority: [H]igh, [M]edium, [L]ow):
  26. // - [H] Everything: extract functions from Netflix Hide Titles script and insert them below
  27. //
  28. // History:
  29. // --------
  30. // 2019.09.18 [0.1] First test version, private use only
  31. //
  32. //}
  33.  
  34. /*jshint esversion: 6 */
  35.  
  36. const TITLELIST_Version = '0.1';
  37.  
  38. // FUNCTIONS *************************************************************************************************************
  39.  
  40. function TL() {
  41. 'use strict';
  42. var mainContext;
  43.  
  44. /* XXX var private_stuff = function() { // Only visible inside Restaurant()
  45. myPrivateVar = "I can set this here!";
  46. }*/
  47.  
  48. this.getLoggedUser = function(ctx) {
  49. //
  50. // Return name of user currently logged on <ctx> site
  51. // Return last saved value and log error if no user is found
  52. //
  53. var user = ctx.getUser();
  54.  
  55. if (!user) {
  56. console.error(ctx.name + ": user not logged in (or couldn't get user info) on URL " + document.URL);
  57. user = GM_getValue(ctx.name + '-lastUser', '');
  58. console.error("Using last user: " + user);
  59. }
  60. GM_setValue(ctx.name + '-lastUser', user);
  61. ctx.user = user;
  62. return user;
  63. };
  64.  
  65.  
  66. function loadSavedList(listName) {
  67. //
  68. // Load a single saved lists
  69. //
  70. var list;
  71. var userData = GM_getValue(listName, null);
  72. if (userData) {
  73. try {
  74. list = JSON.parse(userData);
  75. } catch(err) {
  76. alert("Error loading saved list named '" + listName + "'!\n" + err.message);
  77. }
  78. }
  79. return list;
  80. }
  81.  
  82.  
  83. this.loadSavedLists = function(ctx) {
  84. //
  85. // Load lists saved for the current user
  86. //
  87. var lists = {};
  88.  
  89. var listNames = loadSavedList('TitleLists-' + ctx.user);
  90. if (!listNames) return lists;
  91.  
  92. for (var listName in listNames) {
  93. lists[listName] = loadSavedList('TitleList-' + ctx.user + '-' + listName);
  94. }
  95. return lists;
  96. };
  97.  
  98.  
  99. this.saveList = function(ctx, list, name) {
  100. //
  101. // Save single list for the current user
  102. //
  103. var listNames = loadSavedList('TitleLists-' + ctx.user);
  104. if (!listNames) listNames = {};
  105.  
  106. listNames[name] = 1;
  107. var userData = JSON.stringify(listNames);
  108. GM_setValue('TitleLists-' + ctx.user, userData);
  109.  
  110. userData = JSON.stringify(list);
  111. GM_setValue('TitleList-' + ctx.user + '-' + name, userData);
  112. };
  113.  
  114.  
  115. this.manageTitlePage = function(ctx) {
  116. var we_are_in_a_title_page = ctx.isTitlePage(document);
  117. if (!we_are_in_a_title_page) return;
  118.  
  119. // find current logged in user, or quit script
  120. if (!getLoggedUser(ctx)) return;
  121.  
  122. mainContext = ctx;
  123.  
  124. // Load lists data for this user from local storage
  125. ctx.allLists = loadSavedLists(dest);
  126.  
  127. // start the title processing function
  128. processTitles(ctx);
  129. if (ctx.interval >= 100) {
  130. ctx.timer = setInterval(function() {processTitles(ctx);}, ctx.interval);
  131. }
  132. };
  133.  
  134.  
  135. this.inLists = function(ctx, tt, entry) {
  136. //
  137. // Receives a title (and corresponding entry) and finds all lists title is in.
  138. // Argument "entry" is for "virtual" lists determined by attributes in the DOM
  139. //
  140. var lists = ( ctx.getListsFromEntry && ctx.getListsFromEntry(tt, entry) || {} );
  141.  
  142. for (var list in ctx.allLists) {
  143. if (ctx.allLists[list][tt.id]) lists[list] = true;
  144. }
  145.  
  146. return lists;
  147. };
  148.  
  149.  
  150. this.processTitles = function(ctx) {
  151. //
  152. // Process all title cards in current page
  153. //
  154. var entries = ctx.getTitleEntries(document);
  155. if (!entries) return;
  156.  
  157. var entry, tt, lists, processingType;
  158. for (var i = 0; i < entries.length; i++) {
  159. entry = entries[i];
  160.  
  161. // if entry has already been previously processed, skip it
  162. if (entry.TLProcessed) continue;
  163.  
  164. tt = ctx.getIdFromEntry(entry);
  165. if (!tt) continue;
  166.  
  167. ctx.modifyEntry(entry);
  168. lists = inLists(tt, entry);
  169.  
  170. processingType = ctx.determineType(lists, tt, entry);
  171.  
  172. if (processingType) {
  173. ctx.processItem(entry, tt, processingType);
  174. entry.TLProcessingType = processingType;
  175. }
  176.  
  177. entry.TLProcessed = true; // set to "true" after processing (so we skip it on next pass)
  178. }
  179. };
  180.  
  181.  
  182. this.toggleTitle = function(evt) {
  183. var data = evt.target.dataset;
  184.  
  185. // get title entry
  186. var entry = evt.target;
  187. if (Number.isInteger(Number(data.howToFindEntry))) {
  188. for (var i = 0; i < Number(data.howToFindEntry); i++) entry = entry.parentNode;
  189. } else {
  190. entry = entry.closest(data.howToFindEntry);
  191. }
  192.  
  193. var tt = mainContext.getIdFromEntry(entry);
  194. if (!tt) return;
  195.  
  196. // check if item is in list
  197. var list = mainContext.allLists[data.toggleList];
  198. if (list[tt.id]) {
  199. delete list[tt.id];
  200. mainContext.unProcessItem(entry, tt, data.toggleType);
  201. entry.TLProcessingType = "-" + data.toggleType;
  202. } else {
  203. list[tt.id] = tt.title;
  204. mainContext.processItem(entry, tt, data.toggleType);
  205. entry.TLProcessingType = data.toggleType;
  206. }
  207. saveList(mainContext, list, data.toggleList);
  208. };
  209.  
  210.  
  211.  
  212. this.addToggleEventOnClick = function(button, toggleType, toggleList, howToFindEntry) {
  213. button.dataset.toggleType = toggleType;
  214. button.dataset.toggleList = toggleList;
  215. button.dataset.howToFindEntry = howToFindEntry;
  216. button.addEventListener('click', toggleTitle, false);
  217. };
  218.  
  219.  
  220. }
  221.  
  222.  
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230. function xxx() {
  231. var WATCHLIST = "watchlist";
  232. var RATINGLIST = "ratings";
  233. var CHECKINS = "checkins";
  234.  
  235. var TITLES = "Titles";
  236. var PEOPLE = "People";
  237. var IMAGES = "Images";
  238. // Lists can be about Titles, People & Images (no Characters lists anymore?)
  239. // Comment out a list type to disable highlighting for it.
  240. var listTypes = {};
  241. listTypes[TITLES] = true;
  242. listTypes[PEOPLE] = true;
  243. //listTypes[IMAGES] = true; // To-do: highlight images using colored borders?
  244.  
  245. var listOrderIdx = [];
  246.  
  247. var myLists = [];
  248. var neededLists = {}; //GUIDO NF
  249.  
  250.  
  251. // Modified version of Michael Leigeber's code, from:
  252. // http://sixrevisions.com/tutorials/javascript_tutorial/create_lightweight_javascript_tooltip/
  253. // http://userscripts.org/scripts/review/91851 & others
  254. 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");';
  255.  
  256. var newJs = document.createElement('script');
  257. newJs.setAttribute('type', 'text/javascript');
  258. newJs.innerHTML = injectJs;
  259. document.getElementsByTagName('head')[0].appendChild(newJs);
  260.  
  261. var myName = 'Netflix hide titles'; // Name & version of this script
  262. var user = ''; // Current user name/alias
  263. var IMDbUser = '';
  264. var interval = 1000; // Interval (in ms, >= 100) to re-scan links in the DOM
  265. // Won't re-scan if < 100
  266. // (I might consider using MutationObserver in the future, instead)
  267.  
  268. function getCurrentNetflixUser() {
  269. //
  270. // Return name of user currently logged on IMDb (log on console if failed)
  271. //
  272. var loggedUser = null;
  273.  
  274. var account = document.querySelector('div.account-menu-item div.account-dropdown-button > a');
  275. if (account) {
  276. var accountString = account.getAttribute("aria-label");
  277. if (accountString) {
  278. loggedUser = accountString.replace(/ - Account & Settings$/, '');
  279. if (loggedUser == accountString) loggedUser == null;
  280. }
  281. }
  282. if (!loggedUser) {
  283. console.error(document.URL + "\nUser not logged in (or couldn't get user info)"); // responseDetails.responseText
  284. loggedUser = GM_getValue('NetflixHide_lastUser', '');
  285. console.error("Using last user: " + loggedUser);
  286. }
  287. GM_setValue("NetflixHide_lastUser", loggedUser);
  288. return loggedUser;
  289. }
  290.  
  291. function getCurrentIMDbUser() {
  292. //
  293. // Return name of user currently logged on IMDb (log on console if failed)
  294. //
  295. var loggedIn = '';
  296. var account = document.getElementById('consumer_user_nav') ||
  297. document.getElementById('nbpersonalize');
  298. if (account) {
  299. var result = account.getElementsByTagName('strong');
  300. if (!result.length) result = account.getElementsByClassName("navCategory");
  301. if (!result.length) result = account.getElementsByClassName("singleLine");
  302. if (!result.length) result = account.getElementsByTagName("p");
  303. if (result)
  304. loggedIn = result[0].textContent.trim();
  305. }
  306. if (!loggedIn)
  307. console.error(document.URL + "\nUser not logged in (or couldn't get user info)"); // responseDetails.responseText
  308. return loggedIn;
  309. }
  310.  
  311. var myLocalList = {};
  312. var myNetflixList = {};
  313.  
  314. function loadMyLocalList() {
  315. //
  316. // Load data for the current user
  317. //
  318. var userData = GM_getValue("NetflixHideList-"+user, null);
  319. if (userData) {
  320. try {
  321. myLocalList = JSON.parse(userData);
  322. return true;
  323. } catch(err) {
  324. alert("Error loading Netflix local data!\n" + err.message);
  325. }
  326. }
  327. }
  328.  
  329. function loadMyNetflixList() {
  330. //
  331. // Load data for the current user
  332. //
  333. var userData = GM_getValue("NetflixMyList-"+user, null);
  334. if (userData) {
  335. try {
  336. myNetflixList = JSON.parse(userData);
  337. return true;
  338. } catch(err) {
  339. alert("Error loading Netflix My List data!\n" + err.message);
  340. }
  341. }
  342.  
  343. return false;
  344. }
  345.  
  346. function getMyIMDbLists() {
  347. //
  348. // Get all lists (name & id) for current user into myLists array
  349. // and set default colors for them (if not previously defined)
  350. //
  351.  
  352. // You can customize your lists colors.
  353. // See also the listOrder variable below.
  354. // After any change in the code: save the script, reload the lists page,
  355. // clear the highlight data and refresh the highlight data!
  356. var customColors = [];
  357. customColors["Your Watchlist"] = "DarkGoldenRod";
  358. customColors["Your ratings" ] = "Green";
  359. customColors["Your check-ins"] = "DarkGreen";
  360. //GUIDO customColors["DefaultColor" ] = "DarkCyan";
  361. customColors["DefaultColor" ] = "Maroon";
  362. customColors["DefaultPeople" ] = "DarkMagenta";
  363. //GUIDO customColors["Filmes Netflix Brasil"] = "Red";
  364. customColors["Visti"] = "seagreen";
  365. customColors["Parzialmente visti"] = "yellowgreen";
  366. customColors["no"] = "darkgrey";
  367.  
  368. // You can set the search order for the highlight color when a title is in multiple lists.
  369. // The script will choose the color of the the first list found in the variable below.
  370. // Uncomment the line below and enter the names of any lists you want to give preference over the others.
  371. var listOrder = ["Your Watchlist", "Your ratings"];
  372.  
  373. myLists.length = 0; // Clear arrays and insert the two defaults
  374. myLists.push({"name":"Your Watchlist", "id":WATCHLIST, "color":customColors["Your Watchlist"] || "", "ids":{}, "type":TITLES });
  375. myLists.push({"name":"Your ratings", "id":RATINGLIST, "color":customColors["Your ratings"] || "", "ids":{}, "type":TITLES });
  376. myLists.push({"name":"Your check-ins", "id":CHECKINS, "color":customColors["Your check-ins"] || "", "ids":{}, "type":TITLES });
  377. var lists = document.getElementsByClassName('user-list');
  378. if (!lists || lists.length < 1) {
  379. console.error("Error getting lists (or no lists exist)!");
  380. return false;
  381. }
  382. for (var i = 0; i < lists.length; i++) {
  383. var listType = lists[i].getAttribute("data-list-type");
  384. if (listType in listTypes) {
  385. var tmp = lists[i].getElementsByClassName("list-name");
  386. if (!tmp) {
  387. console.error("Error reading information from list #"+i);
  388. continue;
  389. }
  390. tmp = tmp[0]; // <a class="list-name" href="/list/ls003658871/">Filmes Netflix Brasil</a>
  391. var name = tmp.text;
  392. var id = tmp.href.match(/\/list\/([^\/\?]+)\/?/)[1];
  393. var colorType = listType == PEOPLE ? "DefaultPeople" : "DefaultColor";
  394. var color = customColors[name] || customColors[colorType] || "";
  395. myLists.push({"name":name, "id":id, "color":color, "ids":{}, "type":listType });
  396. }
  397. }
  398. setListOrder(listOrder);
  399. return true;
  400. }
  401.  
  402. function loadMyIMDbLists() {
  403. //
  404. // Load data for the current user
  405. //
  406. // var userData = localStorage.getItem("myMovies-"+user); // GUIDO NF
  407. var userData = GM_getValue("myIMDbMovies-"+user, null); // GUIDO NF
  408. if (userData) {
  409. try {
  410. myLists = JSON.parse(userData);
  411. if ("myLists" in myLists) {
  412. listOrderIdx = myLists["listOrder"];
  413. myLists = myLists["myLists" ];
  414.  
  415. // GUIDO NF
  416. for (var i = 0; i < myLists.length; i++) {
  417. if (myLists[i].type != TITLES) continue;
  418. switch (myLists[i].name) {
  419. case 'no': neededLists.no = i; break;
  420. case 'Visti': neededLists.visti = i; break;
  421. case 'Your Watchlist': neededLists.watch = i; break;
  422. case 'tbd': neededLists.tbd = i; break;
  423. }
  424. }
  425. // FINE GUIDO NF
  426. }
  427. return true;
  428. } catch(err) {
  429. alert("Error loading previous data!\n" + err.message);
  430. }
  431. }
  432. return false;
  433. }
  434.  
  435. function saveMyLocalList() {
  436. //
  437. // Save data for the current user
  438. //
  439. var userData = JSON.stringify(myLocalList);
  440. GM_setValue("NetflixHideList-"+user, userData);
  441. }
  442.  
  443. function saveMyIMDbLists() {
  444. //
  445. // Save data for the current user
  446. //
  447. var userData = JSON.stringify(myLocalList);
  448. GM_setValue("NetflixHideList-"+user, userData);
  449.  
  450. userData = {"listOrder": listOrderIdx, "myLists": myLists};
  451. userData = JSON.stringify(userData);
  452. GM_setValue("myIMDbMovies-"+user, userData);
  453. }
  454.  
  455. function getIdFromDiv(div) {
  456. var a = div.querySelector('a[href^="/watch/"]');
  457. var tt = null;
  458. if (a) {
  459. tt = a.href.match(/\/watch\/([^/?&]+)[/?&]/);
  460. if (tt && tt.length >= 2) tt = tt[1];
  461. }
  462. if (!tt) console.error('Could not determine title id :-(');
  463. return tt;
  464. }
  465.  
  466. function toggleTitle(evt) {
  467. // get title id
  468. var div = evt.target.parentNode.parentNode;
  469. var tt = getIdFromDiv(div);
  470.  
  471. // check if item is in list
  472. if (myLocalList[tt]) {
  473. delete myLocalList[tt];
  474. showItem(div, tt);
  475. } else {
  476. var movie = div.querySelector("div.fallback-text");
  477. var movieTitle = '';
  478. if (movie) movieTitle = movie.innerText;
  479. if (!movieTitle) movieTitle = tt;
  480. myLocalList[tt] = movieTitle;
  481. hideItem(div, tt, movieTitle);
  482. }
  483. saveMyLocalList();
  484. console.log('TOGGLE: ' + tt + ', t: ' + movieTitle);
  485. }
  486.  
  487.  
  488. var hideTypes = {
  489. "H": { "name": 'Hidden', "colour": 'white' },
  490. "D": { "name": 'Disliked', "colour": 'black' },
  491. "W": { "name": 'Watchlist', "colour": 'darkgoldenrod', "visible": true },
  492. "T": { "name": 'TBD', "colour": 'Maroon', "visible": true },
  493. "S": { "name": 'Watched', "colour": 'seagreen' },
  494. "N": { "name": 'NO', "colour": 'darkgrey' },
  495. "M": { "name": 'My list', "colour": 'yellow' },
  496. "MISSING": { "name": 'Hide type not known', "colour": 'red' },
  497. };
  498.  
  499.  
  500. function hideItem(div, id, title, hideType) {
  501. //console.log('hideItem', id, title, hideType);
  502. if (!hideType) hideType = 'H';
  503.  
  504. if (!hideTypes[hideType]) hideType = 'MISSING';
  505. var triangle = document.createElement('div');
  506. triangle.className = 'NHT-triangle'
  507. triangle.style.cssText =
  508. 'border-right: 20px solid ' + hideTypes[hideType].colour + '; ' +
  509. 'border-bottom: 20px solid transparent;' +
  510. 'height: 0; ' +
  511. 'width: 0; ' +
  512. 'position: absolute; ' +
  513. 'top: 0; ' +
  514. 'right: 0; ' +
  515. 'z-index: 2;'
  516. triangle.title = hideTypes[hideType].name;
  517. div.parentNode.appendChild(triangle);
  518.  
  519. if (!hideTypes[hideType].visible) div.parentNode.style.opacity = .1;
  520. /*
  521. var parent = div.parentNode;
  522. parent.parentNode.style.width = '5%';
  523.  
  524. var field = parent.querySelector('fieldset#hideTitle' + id);
  525. if (!field) {
  526. field = document.createElement('fieldset');
  527. field.id = 'hideTitle' + id;
  528. field.style.border = 0;
  529. field.appendChild(document.createTextNode(title));
  530. parent.appendChild(field);
  531. } else {
  532. field.style.display = 'block';
  533. }
  534. */
  535. }
  536.  
  537. function showItem(div, id) {
  538. div.parentNode.style.opacity = 1;
  539. var triangle = div.parentNode.querySelector('.NHT-triangle');
  540. if (triangle) triangle.parentNode.removeChild(triangle);
  541. /*
  542. div.parentNode.parentNode.style.width = null;
  543. div.parentNode.querySelector('fieldset#hideTitle' + id).style.display = 'none';
  544. */
  545. }
  546.  
  547. /* FROM IMDB MY MOVIES ENHANCER */
  548. function eraseMyData() {
  549. //
  550. // Erase just the movies and lists information for the user
  551. //
  552. // localStorage.removeItem("myMovies-"+user); // GUIDO NF
  553. GM_deleteValue("myMovies-"+user); // GUIDO NF
  554. for (var i = 0; i < myLists.length; i++)
  555. myLists[i].ids = {};
  556. }
  557.  
  558. function parseCSV(str) {
  559. // Simple CSV parsing function, by Trevor Dixon:
  560. // https://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data
  561. var arr = [];
  562. var quote = false; // true means we're inside a quoted field
  563.  
  564. // iterate over each character, keep track of current row and column (of the returned array)
  565. var row, col, c;
  566. for (row = col = c = 0; c < str.length; c++) {
  567. var cc = str[c], nc = str[c+1]; // current character, next character
  568. arr[row] = arr[row] || []; // create a new row if necessary
  569. arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary
  570.  
  571. // If the current character is a quotation mark, and we're inside a
  572. // quoted field, and the next character is also a quotation mark,
  573. // add a quotation mark to the current column and skip the next character
  574. if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
  575.  
  576. // If it's just one quotation mark, begin/end quoted field
  577. if (cc == '"') { quote = !quote; continue; }
  578.  
  579. // If it's a comma and we're not in a quoted field, move on to the next column
  580. if (cc == ',' && !quote) { ++col; continue; }
  581.  
  582. // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
  583. // and move on to the next row and move to column 0 of that new row
  584. if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
  585.  
  586. // If it's a newline (LF or CR) and we're not in a quoted field,
  587. // move on to the next row and move to column 0 of that new row
  588. if (cc == '\n' && !quote) { ++row; col = 0; continue; }
  589. if (cc == '\r' && !quote) { ++row; col = 0; continue; }
  590.  
  591. // Otherwise, append the current character to the current column
  592. arr[row][col] += cc;
  593. }
  594. return arr;
  595. }
  596.  
  597. var downloadedLists = 0;
  598. var listsNotDownloaded = [];
  599.  
  600.  
  601. function advanceProgressBar() {
  602. //
  603. // Update progress bar
  604. //
  605. downloadedLists += 1;
  606. var total = myLists.length;
  607. var p = Math.round(downloadedLists*(100/total));
  608. updateProgressBar(p, "Loaded "+downloadedLists+"/"+total);
  609. if (downloadedLists >= total) {
  610. updateProgressBar(0, "");
  611. if (listsNotDownloaded.length > 0) {
  612. var msg = "Done, but could not load list(s):";
  613. listsNotDownloaded.forEach(function(l) { msg += "\n * " + l;} );
  614. msg += "\n\nThis script can only read public lists.";
  615. alert(msg);
  616. } else
  617. alert("OK, we're done!");
  618. }
  619. }
  620.  
  621. function downloadOK(idx, request, link) {
  622. //
  623. // Process a downloaded list
  624. //
  625. if (request.status != 200) {
  626. console.error("Error "+request.status+" downloading "+link+": " + request.statusText);
  627. } else
  628. if (request.responseText.indexOf("<!DOCTYPE html") >= 0) {
  629. console.error("Received HTML instead of CSV file from "+link);
  630. } else {
  631. var data = parseCSV(request.responseText);
  632. var res, entryCode;
  633. var fields = {};
  634. var type = myLists[idx].type;
  635. for (var i=1; i < data.length; i++) {
  636. if (type == TITLES) {
  637. // ___0___ _____1_____ ____2_____ ___3____ _____4_____ ____5_____ _____6_____ ______7_______ _____8_____ _______9______ ____10___ _____11_____ ___12____ _____13_____ ____14____
  638. // ratings : Const, Your Rating, Date Added, Title, URL, Title Type, IMDb Rating, Runtime (mins), Year, Genres, Num Votes, Release Date, Directors
  639. // others : Position, Const, Created, Modified, Description, Title, URL, Title Type, IMDb Rating, Runtime (mins), Year, Genres, Num Votes, Release Date, Directors
  640. for (var f=0; f < data[0].length; f++)
  641. { fields[data[0][f]] = data[i][f]; }
  642. // var tt = fields["Const"]; // GUIDO NF
  643. var tt = fields["Title"]; // GUIDO NF
  644. var ratingMine = fields["Your Rating"];
  645. var ratingIMDb = fields["IMDb Rating"];
  646. if (typeof tt === "undefined") console.error("Error processing line "+i+" of "+idx);
  647. // else if (tt.substr(0,2) != 'tt') console.error("Error getting IMDb const from: "+data[i]); // GUIDO NF
  648. else {
  649. // var ttNum = parseInt(tt.substr(2)); // GUIDO NF
  650. // Encode the movie number with "base 36" to save memory
  651. // entryCode = ttNum.toString(36); // GUIDO NF
  652. entryCode = tt; // GUIDO NF
  653. myLists[idx].ids[entryCode] = {m:ratingMine, i:ratingIMDb};
  654. }
  655. } else if (type == PEOPLE) {
  656. // ___0___ __1__ ___2___ ___3____ _____4_____ __5__ ____6____ ____7_____
  657. // Position, Const, Created, Modified, Description, Name, Known For, Birth Date
  658. for (var f=0; f < data[0].length; f++)
  659. { fields[data[0][f]] = data[i][f]; }
  660. var nm = fields["Const"];
  661. //var name = fields["Name"];
  662. if (typeof nm === "undefined") console.error("Error processing line "+i+" of "+idx);
  663. else if (nm.substr(0,2) != 'nm') console.error("Error getting IMDb const from: "+data[i]);
  664. else {
  665. var nmNum = parseInt(nm.substr(2));
  666. // Encode the entry with "base 36" to save memory
  667. entryCode = nmNum.toString(36);
  668. //myLists[idx].ids[entryCode] = {n: name};
  669. myLists[idx].ids[entryCode] = {};
  670. }
  671. } else if (type == IMAGES) {
  672. // Do nothing for now
  673. }
  674. }
  675. // Save data into browser
  676. saveMyIMDbLists();
  677. }
  678.  
  679. advanceProgressBar() ;
  680.  
  681. // Try to free some memory
  682. delete request.responseText;
  683. }
  684.  
  685. var createFunction = function( func, p1, p2, p3 ) {
  686. return function() {
  687. func(p1, p2, p3);
  688. };
  689. };
  690.  
  691. function downloadError(name, request, link) {
  692. //
  693. // Alert user about a download error
  694. //
  695. var msg = "Error downloading your list "+name+":\n"+
  696. "Status: " +request.status + " - " + request.statusText +":\n"+
  697. "Source: " +link +"\n" +
  698. "Headers: " +request.getAllResponseHeaders();
  699. alert(msg);
  700. console.error(msg);
  701. updateProgressBar(0, "");
  702. }
  703.  
  704. function downloadAsync(name, idx, exportLink) {
  705. var request = new XMLHttpRequest();
  706. request.onload = createFunction(downloadOK, idx, request, exportLink);
  707. request.onerror = createFunction(downloadError, name, request, exportLink);
  708. request.open("GET", exportLink, true);
  709. //request.setRequestHeader("Accept-Encoding","gzip"); // Browser does this already? (I get 'Refused to set unsafe header "Accept-Encoding"')...
  710. request.send();
  711. }
  712.  
  713. function downloadAsyncWatchlist(name, idx, url) {
  714. var request = new XMLHttpRequest();
  715. request.onload = function() {
  716. var exportLink;
  717. var id = request.responseText.match('<meta property="pageId" content="(ls.+?)"/>');
  718. if (id && id.length > 1)
  719. exportLink = document.location.protocol + "//www.imdb.com/list/"+id[1]+"/export";
  720. else {
  721. id = request.responseText.match('"list":{"id":"(ls.+?)"');
  722. if (id && id.length > 1)
  723. exportLink = document.location.protocol + "//www.imdb.com/list/"+id[1]+"/export";
  724. }
  725. if (exportLink)
  726. downloadAsync(name, idx, exportLink);
  727. else {
  728. console.error("Could not find id of the '"+name+"' list! Try to make it public (you can make it private again right after).");
  729. listsNotDownloaded.push(name);
  730. advanceProgressBar();
  731. }
  732. };
  733. request.onerror = createFunction(downloadError, name, request, url);
  734. request.open("GET", url, true);
  735. request.send();
  736. }
  737.  
  738. function downloadList(idx) {
  739. //
  740. // Download a list
  741. //
  742. var ur = document.location.pathname.match(/\/(ur\d+)/);
  743. if (ur && ur[1])
  744. ur = ur[1];
  745. else {
  746. alert("Sorry, but I could not find your user ID (required to download your lists). :(");
  747. return;
  748. }
  749.  
  750. var name = myLists[idx].name;
  751. var id = myLists[idx].id;
  752. // Watchlist & check-ins are not easily available (requires another fetch to find export link)
  753. // http://www.imdb.com/user/ur???????/watchlist/export | shows old HTML format
  754. // http://www.imdb.com/list/export?list_id=watchlist&author_id=ur??????? | 404 error
  755. // http://www.imdb.com/user/ur???????/watchlist | HTML page w/ "export link" at the bottom
  756. if (id == WATCHLIST || id == CHECKINS) {
  757. var url = document.location.protocol + "//www.imdb.com/user/"+ur+"/"+id;
  758. downloadAsyncWatchlist(name, idx, url);
  759. } else {
  760. var exportLink;
  761. if (id == RATINGLIST)
  762. exportLink = document.location.protocol + "//www.imdb.com/user/"+ur+"/"+id+"/export";
  763. else exportLink = document.location.protocol + "//www.imdb.com/list/"+id+"/export";
  764. downloadAsync(name, idx, exportLink);
  765. }
  766. }
  767.  
  768. function downloadLists() {
  769. //
  770. // Begin to download all user lists at once (asynchronously)
  771. //
  772. downloadedLists = 0;
  773. for (var idx=0; idx < myLists.length; idx++)
  774. downloadList(idx);
  775. // With 10.000 items in 5 lists, the approx. time to download them (on Chrome 29) was:
  776. // - synchronously: 1:50s
  777. // - asynchronously: 30s
  778. // Results might vary - a lot! - depending on number of lists and browser
  779. // Connections per hostname seems to be around 6: http://www.browserscope.org/?category=network&v=top
  780. }
  781.  
  782. // Really simple progress bar...
  783. var pb;
  784. var pbBox;
  785. var pbTxt;
  786.  
  787. function createProgressBar(p, msg) {
  788. var top_ = Math.round(window.innerHeight / 2) -15;
  789. var left = Math.round(window.innerWidth / 2) -100;
  790. pbBox = document.createElement('div');
  791. pbBox.style.cssText = "background-color: white; border: 2px solid black; "+
  792. "position: fixed; height: 30px; width: 200px; top: "+top_+"px; left: "+left+"px;";
  793. document.body.appendChild(pbBox);
  794.  
  795. pb = document.createElement('div');
  796. pb.style.cssText = "background-color: green; border: none; height: 100%; width: "+p+"%;";
  797. pbBox.appendChild(pb);
  798.  
  799. pbTxt = document.createElement('div');
  800. pbTxt.textContent = msg;
  801. pbTxt.style.cssText = "text-align: center; margin-top: -25px; font-family: verdana,sans-serif;";
  802. pbBox.appendChild(pbTxt);
  803. }
  804.  
  805. function updateProgressBar(p, msg) {
  806. if (p <= 0) {
  807. pbBox.style.display = "none";
  808. return;
  809. }
  810. pbTxt.textContent = msg;
  811. pb.style.width = p+"%";
  812. }
  813.  
  814. function setListOrder(listOrder) {
  815. //
  816. // Set color highlight order using lists indices, after variable listOrder (containing lists names).
  817. //
  818. if (typeof listOrder == "undefined")
  819. listOrder = []; // array of lists names
  820.  
  821. listOrderIdx = []; // array of lists indices
  822.  
  823. // First add indices set by user in listOrder
  824. for (var j = 0; j < listOrder.length; j++)
  825. for (var i = 0; i < myLists.length; i++)
  826. if (myLists[i].name == listOrder[j]) {
  827. listOrderIdx.push(i);
  828. break;
  829. }
  830. // Add remaining indices
  831. for (var i = 0; i < myLists.length; i++)
  832. if (!listOrderIdx.includes(i))
  833. listOrderIdx.push(i);
  834. }
  835.  
  836.  
  837.  
  838.  
  839. function inLists(num, type) {
  840. //
  841. // Receives an IMDb code and return the names of lists containing it.
  842. // Argument "num" : entry number encoded in base 36
  843. // Argument "type": optional, if set, limits search to a specific type of list
  844. //
  845. var num_l = 0;
  846. var lists = "";
  847. var pos = -1;
  848. var rated = false;
  849. var imdbRating = "";
  850. var header = "";
  851. var movie, name;
  852. for (var i = 0; i < myLists.length; i++) {
  853. if (type && myLists[i].type != type)
  854. continue;
  855. movie = myLists[i].ids[num];
  856. if (movie) {
  857. if (num_l)
  858. lists += "<br>";
  859. name = myLists[i].name;
  860. imdbRating = movie.i;
  861. if (imdbRating && name == "Your ratings") {
  862. name = "Your ratings: " + movie.m + " (IMDb: " + imdbRating + ")";
  863. rated = true;
  864. }
  865. lists += name;
  866. num_l += 1;
  867. }
  868. }
  869. if (imdbRating && !rated)
  870. imdbRating = "IMDb rating: " + imdbRating + "<br>";
  871. else imdbRating = "";
  872. if (num_l == 1)
  873. header = "<b>In your list:</b><br>";
  874. else header = "<b>In "+num_l+" of your lists:</b><br>";
  875.  
  876. return imdbRating + header + '<div style="margin-left: 15px">' + lists + '</div>';
  877. }
  878.  
  879. /* END */
  880.  
  881.  
  882. function addHideBtn(div, func, txt, help) {
  883. var b = document.createElement('a');
  884. b.className = "nf-svg-button simpleround";
  885. b.textContent = txt;
  886. b.title = help;
  887. var d = document.createElement('div');
  888. d.className = "nf-svg-button-wrapper";
  889. d.style.cssText = 'bottom: 0; position: absolute; z-index: 10';
  890. d.appendChild(b);
  891. b.addEventListener('click', func, false);
  892. div.appendChild(d);
  893. return d;
  894. }
  895.  
  896. function refreshMovieData() {
  897. alert(myName+"\n\n"+IMDbUser+", I'll get some info from IMDb to be able to highlight your movies,\nplease click [OK] and wait a bit...");
  898. eraseMyData();
  899. createProgressBar(0, "Loading 1/"+myLists.length+"...");
  900. downloadLists();
  901. }
  902.  
  903. function eraseNetflixMyListData() {
  904. GM_deleteValue("NetflixMyList-"+user);
  905. myNetflixList = {};
  906. }
  907.  
  908. function refreshNetflixMyListData() {
  909. eraseNetflixMyListData();
  910. var list = {};
  911.  
  912. var gallery = document.querySelector('div.mainView div.gallery');
  913. var titles = gallery.querySelectorAll('div.title-card');
  914.  
  915. for (var i=0; i < titles.length; i++) {
  916. var a = titles[i];
  917.  
  918. var tt = getIdFromDiv(a);
  919. var movie = a.querySelector("div.fallback-text");
  920. var movieTitle = '';
  921. if (movie) movieTitle = movie.innerText;
  922. if (!movieTitle) movieTitle = tt;
  923. list[tt] = movieTitle;
  924. }
  925.  
  926. var userData = JSON.stringify(list);
  927. GM_setValue("NetflixMyList-"+user, userData);
  928. alert('Netflix My list saved');
  929. }
  930.  
  931. //IMDb
  932. var btn1; // refresh
  933. var btn2; // clear
  934. var btn4; // help
  935. //Netflix
  936. var btn8; // refresh
  937. var btn16; // clear
  938.  
  939. function btnRefresh() {
  940. refreshMovieData();
  941. }
  942.  
  943. function btnClear() {
  944. eraseMyData();
  945. alert(myName+"\n\nDone! Information cleared, so highlighting is now disabled.");
  946. window.location.reload();
  947. }
  948.  
  949. function btnNFRefresh() {
  950. refreshNetflixMyListData();
  951. }
  952.  
  953. function btnNFClear() {
  954. eraseNetflixMyListData();
  955. alert(myName+"\n\nDone! Information cleared.");
  956. window.location.reload();
  957. }
  958.  
  959. function btnHelp () {
  960. alert(myName+"\n\nThis is a user script that:\n"+
  961. " • highlights links for entries in your lists (e.g., movies, series & people)\n"+
  962. " • shows in which of your lists an entry is (in a tooltip)\n"+
  963. "\nIn order to highlight the entries "+
  964. "in all IMDb pages as fast as possible, we need to download "+
  965. "the data from your lists into your browser. Unfortunately " +
  966. "this can be slow, so it is not done automatically. I suggest "+
  967. "you to update this information at most once a day.\n\n" +
  968. "[Refresh highlight data] updates the data in your browser.\n" +
  969. "[Clear highlight data] disables color highlighting.\n"
  970. );
  971. }
  972.  
  973. function addBtn(div, func, txt, help, style) {
  974. var b = document.createElement('button');
  975. b.className = "btn";
  976. if (!style) style = "margin-right: 10px; font-size: 11px;"
  977. b.style.cssText = style;
  978. // b.textContent = txt; // GUIDO NF
  979. b.textContent = 'NF ' + txt; // GUIDO NF
  980. b.title = help;
  981. b.addEventListener('click', func, false);
  982. div.appendChild(b);
  983. return b;
  984. }
  985.  
  986. function addIMDbButtons() {
  987. var main = document.getElementById("main");
  988. if (!main)
  989. console.error('Could not find "main <div>" to insert buttons!');
  990. else {
  991. var h1 = main.getElementsByTagName("h1");
  992. if (h1) {
  993. var div = document.createElement('div');
  994. div.className = "aux-content-widget-2";
  995. div.style.cssText = "margin-top: 10px;";
  996. btn1 = addBtn(div, btnRefresh, "Refresh highlight data", "Reload information from your lists - might take a few seconds");
  997. btn2 = addBtn(div, btnClear, "Clear highlight data", "Disable color highlighting of your lists");
  998. btn4 = addBtn(div, btnHelp, "What's this?", "Click for help on these buttons");
  999. h1[0].appendChild(div);
  1000. } else console.error('Could not find "<h1>Your Lists</h1>" to insert buttons!');
  1001. }
  1002. }
  1003. function addNetflixButtons() {
  1004. var main = document.querySelector('div.mainView');
  1005. if (!main)
  1006. console.error('Could not find "main <div>" to insert buttons!');
  1007. else {
  1008. var div = document.createElement('div');
  1009. var btnStyle = 'margin-left: 20px; margin-bottom: 20px; font-size: 13px; padding: .5em; background: 0 0; color: grey; border: solid 1px grey;';
  1010. btn8 = addBtn(div, btnNFRefresh, "Refresh My List data", "Reload information from your list - might take a few seconds", btnStyle);
  1011. btn16 = addBtn(div, btnNFClear, "Clear My List data", "Empty the data", btnStyle);
  1012. main.appendChild(div);
  1013. }
  1014. }
  1015.  
  1016. /*
  1017. function addButtons() {
  1018. var div = document.querySelector(".jawbone-actions");
  1019. if (!div)
  1020. console.error('Could not find "button <div>" to insert buttons!');
  1021. else {
  1022. btn1 = addBtn(div, toggleTitle, "HIDE", "Hide this title");
  1023. }
  1024. }
  1025. */
  1026.  
  1027. //-------- "main" --------
  1028. var we_are_in_a_title_page = false;
  1029. var we_are_in_the_imdb_list_page = false;
  1030. var we_are_in_the_netflix_list_page = false;
  1031.  
  1032. if (document.location.href.match(/\.netflix\..{2,3}\//)) {
  1033. we_are_in_a_title_page = true;
  1034. }
  1035.  
  1036. if (document.location.href == 'https://www.netflix.com/browse/my-list') {
  1037. we_are_in_the_netflix_list_page = true;
  1038. }
  1039. if (document.location.href.match(/\.imdb\..{2,3}\/user\/[^\/]+\/lists/)) {
  1040. we_are_in_the_imdb_list_page = true;
  1041. getMyIMDbLists();
  1042. }
  1043.  
  1044. // Find current logged in user, or quit script
  1045. user = getCurrentNetflixUser();
  1046. if (!user) return;
  1047.  
  1048.  
  1049. // Allow user to manually update his/her lists
  1050. if (we_are_in_the_imdb_list_page) {
  1051. // Find current logged in user, or quit script
  1052. IMDbUser = getCurrentIMDbUser();
  1053. if (!IMDbUser) return; // FIX-ME: to support external sites: set/get LAST user to/from browser storage
  1054.  
  1055. addIMDbButtons();
  1056. return; // Nothing else to do on the lists page - goodbye!
  1057. }
  1058. if (we_are_in_the_netflix_list_page) {
  1059. addNetflixButtons();
  1060. }
  1061.  
  1062. if (we_are_in_a_title_page) {
  1063. // Load lists data for this user from localStorage
  1064. loadMyLocalList();
  1065. loadMyNetflixList();
  1066. loadMyIMDbLists();
  1067. }
  1068.  
  1069.  
  1070.  
  1071. // THIS IS THE NEW PART
  1072.  
  1073.  
  1074. function hideTitleCards() {
  1075. // console.log('waitnlp',we_are_in_the_netflix_list_page);
  1076. //
  1077. // Highlight all title cards in the current Netflix page
  1078. //
  1079.  
  1080. var num, color, lists, movie;
  1081. var anchors = document.querySelectorAll('div.title-card');
  1082.  
  1083. for (var i=0; i < anchors.length; i++) {
  1084. var a = anchors[i];
  1085. if (!a.GVhide) {
  1086. addHideBtn(a, toggleTitle, 'H', 'Hide/show this title');
  1087.  
  1088. var tt = getIdFromDiv(a);
  1089. var title, movieTitle;
  1090. var hideType = null;
  1091. if (a.className.indexOf('is-disliked') != -1) hideType = 'D';
  1092. else {
  1093. movie = a.querySelector(".fallback-text");
  1094. if (movie) movieTitle = movie.innerText;
  1095.  
  1096. if (movieTitle) {
  1097. num = movieTitle;
  1098. //lists = inLists(num, TITLES);
  1099. if (myLists[neededLists.watch].ids[num]) {
  1100. hideType = 'W';
  1101. } else if (myLists[neededLists.tbd].ids[num]) {
  1102. hideType = 'T';
  1103. } else if (myLists[neededLists.visti].ids[num]) {
  1104. hideType = 'S';
  1105. } else if (myLists[neededLists.no].ids[num]) {
  1106. hideType = 'N';
  1107. }
  1108. }
  1109. }
  1110. if (!hideType && (title = myLocalList[tt])) hideType = 'H';
  1111. if ((!hideType || hideType == 'W') && !we_are_in_the_netflix_list_page && (title = myNetflixList[tt])) {
  1112. // console.log('ht',hideType,'tt',tt,'mt',movieTitle);
  1113. var row = a.closest('div.lolomoRow');
  1114. if (!row || (row.dataset.listContext != 'queue' && row.dataset.listContext != 'continueWatching')) hideType = 'M';
  1115. // console.log('rownull',!row,'lt',row.dataset.listContext);
  1116. }
  1117.  
  1118. if (hideType) hideItem(a, tt, title, hideType);
  1119. a.GVhide = true; // set to "true" when "enhanced" (so we skip it on next pass)
  1120. }
  1121. }
  1122. }
  1123.  
  1124.  
  1125.  
  1126. // start the hiding title function
  1127. // if (myLists.length) {
  1128. hideTitleCards();
  1129. if (interval >= 100) setInterval(hideTitleCards, interval);
  1130. // }
  1131.  
  1132. }