IMDb List Helper

Makes creating IMDb lists more efficient and convenient

当前为 2017-12-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name IMDb List Helper
  3. // @namespace imdb
  4. // @description Makes creating IMDb lists more efficient and convenient
  5. // @version 2.4.1
  6. // @include http://*imdb.com/list/*/edit*
  7. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-csv/0.71/jquery.csv-0.71.min.js
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10.  
  11. var jQuery = unsafeWindow.jQuery;
  12. var $ = jQuery;
  13.  
  14. //
  15. // CHANGELOG
  16. //
  17. // 2.4.1
  18. // bugfix: In some instances the script wasn't loaded (bad @include)
  19. // change: Limit number of setTimeOut calls
  20. //
  21. // 2.4.0
  22. // bugfix: IMDb changed layout
  23. //
  24. // 2.3.0
  25. // bugfix: importing ratings works again
  26. //
  27. // 2.2.0
  28. // added: support for people
  29. //
  30. // 2.1.1
  31. // added: only show import form if ratings is selected
  32. //
  33. // 2.1
  34. // added: importers for imdb, rateyourmusic, criticker
  35. //
  36. // 2.0
  37. // added: import ratings
  38. // added: if regex doesn't match, skip entry
  39. //
  40. // 1.6.1.2
  41. // added: input text suggestion as a placeholder
  42. //
  43. // 1.6.1.1
  44. // fixed: some entries are skipped when adding imdb ids/urls
  45. //
  46.  
  47. //
  48. // milliseconds between each request
  49. //
  50. var REQUEST_DELAY = 1000;
  51.  
  52. function processImdb(file) {
  53. var csv_lines = file.split("\n");
  54. for(var i = 1; i < csv_lines.length; ++i) {
  55. try {
  56. var data = jQuery.csv.toArray(csv_lines[i]);
  57. var rating = data[8];
  58. if(rating === "") {
  59. continue;
  60. }
  61. $("#filmList").append(rating + "," + data[1] + "\n");
  62. }
  63. catch(e) {
  64. console.log("Exception: " + e);
  65. console.log("Bad line: " + csv_lines[i]);
  66. }
  67. }
  68. }
  69.  
  70. function processRym(file) {
  71. var csv_lines = file.split("\n");
  72. for(var i = 1; i < csv_lines.length; ++i) {
  73. try {
  74. var data = jQuery.csv.toArray(csv_lines[i]);
  75. if(data.length < 6) {
  76. continue;
  77. }
  78. $("#filmList").append(data[4] + "," + data[1] + "\n");
  79. }
  80. catch(e) {
  81. console.log("Exception: " + e);
  82. console.log("Bad line: " + csv_lines[i]);
  83. }
  84. }
  85. }
  86.  
  87. function processCriticker(file) {
  88. var lines = file.split("\n");
  89. for(var i = 1; i < lines.length; ++i) {
  90. var line = lines[i];
  91. var m = line.match(/([1-9]{1}|10)([0-9])?\t(.+)/);
  92. if(m === null) {
  93. continue;
  94. }
  95. var rating = m[1];
  96. var second = m[2];
  97. if(second === undefined) {
  98. // If second value is undefined the rating is < 10 (on criticker)
  99. // and will be changed to 1 because you can't rate 0 on IMDb
  100. rating = "1";
  101. }
  102. $("#filmList").append(rating + "," + m[3] + "\n");
  103. }
  104. }
  105.  
  106. function handleImport(evt) {
  107. var files = evt.target.files;
  108. var file = files[0];
  109. var reader = new FileReader();
  110. reader.onload = function(event){
  111. var file = event.target.result;
  112. var format = $("select[name=import]").val();
  113. if(format === "none") {
  114. alert("Select importer and try again.");
  115. return;
  116. }
  117. else if(format === "imdb") {
  118. processImdb(file);
  119. }
  120. else if(format === "rym") {
  121. processRym(file);
  122. }
  123. else if(format === "criticker") {
  124. processCriticker(file);
  125. }
  126. }
  127.  
  128. reader.readAsText(file);
  129. }
  130.  
  131. var ListManager = {
  132. regex: "^(.*)$",
  133. processRegex: function(rx, cb) {
  134. var filmTitle = rx[1];
  135. cb(filmTitle);
  136. },
  137. handleSelection: function(imdbId, cb) {
  138. cb();
  139. }
  140. };
  141.  
  142. var RatingManager = {
  143. rating: 0,
  144. regex: "^([1-9]{1}|10),(.*)$",
  145. processRegex: function(rx, cb) {
  146. RatingManager.rating = rx[1];
  147. var filmTitle = rx[2];
  148. cb(filmTitle);
  149. },
  150. handleSelection: function(imdbId, cb) {
  151. console.log("RatingManager::handleSelection: Rating " + imdbId);
  152. $.get("http://www.imdb.com/title/" + imdbId, function(data) {
  153. var authHash = $(data).find("#star-rating-widget").data("auth");
  154. var params = {tconst: imdbId, rating: RatingManager.rating,
  155. auth: authHash, tracking_tag: "list",
  156. pageId: imdbId, pageType: "title", subPageType: "main"};
  157. $.post("http://www.imdb.com/ratings/_ajax/title", params, function(data) {
  158. if(data.status !== 200) {
  159. alert("Rating failed. Status code " + data.status);
  160. }
  161. else {
  162. cb();
  163. }
  164. }, "json");
  165. });
  166. }
  167. };
  168.  
  169. var App = {
  170. manager: ListManager,
  171. films: [],
  172. regexObj: null,
  173. isEmpty: function() {
  174. return App.films.length === 0;
  175. },
  176. next: function() {
  177. return App.films.shift();
  178. },
  179. run: function() {
  180. GM_addStyle("#imdbListTextEdit { margin: 0 5% 5% 5%; } \
  181. #imdbListTextEdit input[type=text], #imdbListTextEdit textarea { width: 100%; } \
  182. #imdbListTextEdit textarea { background-color: lightyellow; }");
  183. var textEdit = '<div id="imdbListTextEdit" style="'
  184. + 'padding: 10px; border: 1px solid #e8e8e8">'
  185. + '<p><b>Import mode:</b><input type="radio" name="importmode" value="list" checked="checked">List</input>'
  186. + '<input type="radio" name="importmode" value="ratings">Ratings</input></p>'
  187. + '<textarea id="filmList" rows="7" cols="60" placeholder="Input titles or IMDb IDs and click Start"></textarea><br />'
  188. + '<input type="button" id="doList" value="Start" /> '
  189. + '<input type="button" id="skipFilm" value="Skip" /> '
  190. + '<input type="button" id="retryPost" value="Retry" /> '
  191. + '<span style="font-weight: bold">Remaining: <span id="filmsRemaining">0</span></span><br /><br />'
  192. + '<span style="font-weight: bold;">Current: <input type="text" id="filmCurrent" size="65" style="font-family: monospace" /></span><br />'
  193. + '<span style="font-weight: bold;">Regexp (matches only): <input type="text" value="" id="filmRegexp" size="65" style="font-family: monospace; margin-top: 4px; margin-left: 1px" /></span><br />'
  194. + '<p id="importform" style="display: none"><b>Import from:</b> <select name="import"><option value="none">Select</option><option value="imdb">IMDb</option>'
  195. + '<option value="rym">RateYourMusic</option><option value="criticker">Criticker</option></select><b> File: </b><input type="file" id="fileimport"></p>'
  196. + '</div>';
  197. $("div.lister-search").after(textEdit);
  198. $("#filmRegexp").val(App.manager.regex);
  199. $("#fileimport").on("change", handleImport);
  200. $("#imdbListTextEdit").on("change", "input[name=importmode]", function() {
  201. var value = $(this).val();
  202. if(value === "list") {
  203. App.manager = ListManager;
  204. $("#importform").hide();
  205. }
  206. else {
  207. App.manager = RatingManager;
  208. $("#importform").show();
  209. }
  210. $("#filmRegexp").val(App.manager.regex);
  211. });
  212. // When start button is clicked
  213. $("#imdbListTextEdit").on("click", "#doList", function(e) {
  214. $regexBox = $("#filmRegexp");
  215. if($regexBox.val()) {
  216. App.regexObj = RegExp($regexBox.val());
  217. }
  218. else {
  219. App.regexObj = RegExp(App.manager.regex);
  220. }
  221. // Disable the text area and the button and the regexp box
  222. // as well as the import mode
  223. $filmList = $("#filmList");
  224. $filmList.attr("disabled", "disabled");
  225. $regexBox.attr("disabled", "disabled");
  226. $("#doList").attr("disabled", "disabled");
  227. $("input[name=importmode]").attr("disabled", "disabled");
  228. App.films = $filmList.val().split("\n");
  229. App.handleNext();
  230. });
  231. // when skip button is clicked
  232. $("#imdbListTextEdit").on("click", "#skipFilm", function(e) {
  233. App.handleNext();
  234. });
  235. // Sometimes the request fails forcing the user to skip an entry to continue
  236. $("#imdbListTextEdit").on("click", "#retryPost", function(e) {
  237. $("#add-to-list-search").trigger("keydown");
  238. });
  239. },
  240. reset: function() {
  241. App.films = [];
  242. App.regexObj = null;
  243. $("#filmList").removeAttr("disabled");
  244. $("#filmRegexp").removeAttr("disabled"); // leave regex
  245. $("#doList").removeAttr("disabled");
  246. $("input[name=importmode]").removeAttr("disabled");
  247. $("#filmCurrent").val("");
  248. $("#add-to-list-search", "div.add").val("");
  249. //$("div.results", "div.add").html("");
  250. },
  251. search: function(filmTitle) {
  252. // remove unnecessary whitespace
  253. filmTitle = $.trim(filmTitle);
  254. // set current text to what we're searching
  255. $("#filmCurrent").val(filmTitle);
  256. // remove the first title from the text box and set the remaining number
  257. $filmList = $("#filmList");
  258. var newList = $filmList.val().split("\n");
  259. $("#filmsRemaining").text(newList.length-1);
  260. $filmList.val(newList.slice(1).join("\n"));
  261. // Run regex if it matches and let the manager process the result
  262. var result = App.regexObj.exec(filmTitle);
  263. if(result !== null) {
  264. App.manager.processRegex(result, function(filmTitle) {
  265. // Set imdb search input field to film title
  266. $("#add-to-list-search").val(filmTitle);
  267. // And perform search
  268. $("#add-to-list-search").trigger("keydown");
  269. });
  270. }
  271. else {
  272. App.handleNext();
  273. }
  274. },
  275. handleNext: function() {
  276. if(!App.isEmpty()) {
  277. // if there's more items, search next...
  278. App.search(App.next());
  279. }
  280. else {
  281. // if last film
  282. App.reset();
  283. }
  284. }
  285. };
  286.  
  287. $(document).ready(App.run);
  288.  
  289. // When a search result item is clicked by user or script
  290. $("#add-to-list-search-results").on("click", "a", function(e) {
  291. App.manager.handleSelection($(this).attr("id"), function() {
  292. // Some delay is needed
  293. setTimeout(function() {
  294. App.handleNext();
  295. }, REQUEST_DELAY);
  296. });
  297. });
  298.  
  299. // Monitors for changes to the search result box
  300. // If it recognizes an IMDBb URL/ID, it's clicked automatically
  301. // since there's only one result
  302. var clickId = null;
  303. $("#add-to-list-search-results").bind("DOMNodeInserted", function(e) {
  304. if(clickId === null && $("a", "#add-to-list-search-results").length
  305. && $("#filmCurrent").val().match(/([CHMNTchmnt]{2}[0-9]{7})/) !== null) {
  306. // Some delay is needed for all results to appear
  307. clickId = setTimeout(function() {
  308. $("a", "#add-to-list-search-results").first()[0].click();
  309. clearTimeout(clickId);
  310. clickId = null;
  311. }, REQUEST_DELAY);
  312. }
  313. });