A Chrome extension that gives you finer control over MyAnimeList.net scores
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

433 lines
15 KiB

  1. /* -------------------------------- Globals -------------------------------- */
  2. var MAX_BUCKETS = 256;
  3. var LOADING_IMG = '<img src="http://cdn.myanimelist.net/images/xmlhttp-loader.gif" align="center">';
  4. var should_sort = window.location.href.indexOf("order=4") != -1;
  5. /* ------------------------ Miscellaneous functions ------------------------ */
  6. String.prototype.cut_after = function(substr) {
  7. return this.substr(this.indexOf(substr) + substr.length)
  8. };
  9. String.prototype.cut_before = function(substr) {
  10. return this.substr(0, this.indexOf(substr));
  11. };
  12. function get_anime_id_from_href(href) {
  13. var anime_id;
  14. if (href.indexOf("/anime/") != -1)
  15. anime_id = href.cut_after("/anime/");
  16. else
  17. anime_id = href.cut_after("id=");
  18. if (anime_id.indexOf("/") != -1)
  19. anime_id = anime_id.cut_before("/");
  20. if (anime_id.indexOf("&") != -1)
  21. anime_id = anime_id.cut_before("&");
  22. return anime_id;
  23. }
  24. function get_edit_id_from_href(href) {
  25. var anime_id = href.cut_after("id=");
  26. if (anime_id.indexOf("&") != -1)
  27. anime_id = anime_id.cut_before("&");
  28. return anime_id;
  29. }
  30. function get_score_from_element(elem) {
  31. var score = Math.round(elem.val() * 10) / 10;
  32. if (isNaN(score) || ((score < 1 || score > 10) && score != 0)) {
  33. alert("Invalid score: must be a number between 1.0 and 10.0, or 0.");
  34. return null;
  35. }
  36. if (score == Math.round(score))
  37. score += ".0"
  38. return score;
  39. }
  40. /* --------------------------- Storage functions --------------------------- */
  41. function save_score(anime_id, score) {
  42. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  43. chrome.storage.sync.get(bucket_id, function(data) {
  44. var bucket = data[bucket_id];
  45. if (bucket === undefined)
  46. bucket = data[bucket_id] = {};
  47. bucket[anime_id] = score;
  48. chrome.storage.sync.set(data);
  49. });
  50. }
  51. function retrieve_scores(anime_id, callback) {
  52. var bucket_id = null;
  53. if (anime_id !== null)
  54. bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  55. chrome.storage.sync.get(bucket_id, function(data) {
  56. if (anime_id !== null) {
  57. var bucket = data[bucket_id];
  58. if (bucket !== undefined && bucket[anime_id] !== undefined)
  59. callback(bucket[anime_id]);
  60. else
  61. callback(null);
  62. }
  63. else
  64. callback(data);
  65. });
  66. }
  67. function remove_score(anime_id) {
  68. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  69. chrome.storage.sync.get(bucket_id, function(data) {
  70. var bucket = data[bucket_id];
  71. if (bucket === undefined || bucket[anime_id] === undefined)
  72. return;
  73. delete bucket[anime_id];
  74. if ($.isEmptyObject(bucket))
  75. chrome.storage.sync.remove(bucket_id);
  76. else
  77. chrome.storage.sync.set(data);
  78. });
  79. }
  80. /* ----------------------- Event patches/injections ------------------------ */
  81. function update_list_score(anime_id) {
  82. var new_score = get_score_from_element($("#scoretext" + anime_id));
  83. if (new_score === null)
  84. return;
  85. var payload = {id: anime_id, score: Math.round(new_score)};
  86. $("#scorebutton" + anime_id).prop("disabled", true);
  87. $.post("/includes/ajax.inc.php?t=63", payload, function(data) {
  88. $("#scoreval" + anime_id).text(new_score == 0 ? "-" : new_score);
  89. $("#scoretext" + anime_id).val("");
  90. $("#scorediv" + anime_id).css("display", "none");
  91. $("#scorebutton" + anime_id).prop("disabled", false);
  92. if (should_sort)
  93. sort_list();
  94. update_list_stats();
  95. });
  96. save_score(anime_id, new_score);
  97. }
  98. function update_anime_score(anime_id, is_new) {
  99. var new_score = get_score_from_element($("#myinfo_score"));
  100. if (new_score === null)
  101. return;
  102. var t_id, payload = {score: Math.round(new_score)};
  103. payload["status"] = $("#myinfo_status").val();
  104. payload["epsseen"] = $("#myinfo_watchedeps").val();
  105. if (is_new) {
  106. payload["aid"] = anime_id;
  107. t_id = "61";
  108. }
  109. else {
  110. payload["alistid"] = anime_id;
  111. payload["aid"] = $("#myinfo_anime_id").val();
  112. payload["astatus"] = $("#myinfo_curstatus").val();
  113. t_id = "62";
  114. }
  115. $("#myinfoDisplay").html(LOADING_IMG);
  116. $.post("/includes/ajax.inc.php?t=" + t_id, payload, function(data) {
  117. if (is_new) {
  118. $("#myinfoDisplay").html("");
  119. $("#addtolist").html(data);
  120. }
  121. else
  122. $("#myinfoDisplay").html(data);
  123. });
  124. save_score(anime_id, new_score);
  125. }
  126. function submit_add_form(submit_button) {
  127. var anime_id = $("input[name='series_title']").val();
  128. if (!anime_id)
  129. return submit_button[0].click();
  130. var new_score = get_score_from_element($("#score_input"));
  131. if (new_score === null)
  132. return;
  133. $("select[name='score']").val(Math.round(new_score));
  134. save_score(anime_id, new_score);
  135. submit_button[0].click();
  136. }
  137. function submit_edit_form(anime_id, submit_type, submit_button) {
  138. if (submit_type == 2) {
  139. var new_score = get_score_from_element($("#score_input"));
  140. if (new_score === null)
  141. return;
  142. $("select[name='score']").val(Math.round(new_score));
  143. save_score(anime_id, new_score);
  144. }
  145. else if (submit_type == 3)
  146. remove_score(anime_id);
  147. submit_button[0].click();
  148. }
  149. /* ------------------------ List stats and sorting ------------------------- */
  150. function compare(row1, row2) {
  151. var r1 = $(row1).find("span[id^='scoreval']").text(),
  152. r2 = $(row2).find("span[id^='scoreval']").text();
  153. if (r1 == r2) {
  154. r1 = $(row1).find("a.animetitle span").text();
  155. r2 = $(row2).find("a.animetitle span").text();
  156. return r1 > r2 ? 1 : -1;
  157. }
  158. if (r1 == "-")
  159. return 1;
  160. if (r2 == "-")
  161. return -1;
  162. return r2 - r1;
  163. }
  164. function prepare_list() {
  165. var headers = [".header_cw", ".header_completed", ".header_onhold",
  166. ".header_dropped", ".header_ptw"];
  167. $.each(headers, function(i, header) {
  168. $(header).next()
  169. .nextUntil($(".category_totals").closest("table"))
  170. .wrapAll('<div class="list-chart-group"/>');
  171. });
  172. $(".list-chart-group table").each(function(i, row) {
  173. $(row).add($(row).next())
  174. .wrapAll('<div class="list-chart-row"/>');
  175. });
  176. }
  177. function sort_list() {
  178. $(".list-chart-group").each(function(i, group) {
  179. $(group).find(".list-chart-row").sort(compare).each(function(i, row) {
  180. $(group).append(row);
  181. });
  182. $(group).find(".list-chart-row").each(function(i, row) {
  183. $(row).find("tr").first().children().first().text(i + 1);
  184. $(row).find((i % 2) ? ".td1" : ".td2").toggleClass("td1 td2");
  185. });
  186. });
  187. }
  188. function apply_stats(elem, old_sum, new_sum, nums) {
  189. var old_score = elem.text().cut_after("Score: ").cut_before(",");
  190. var old_dev = elem.text().cut_after("Dev.: ").cut_before("\n");
  191. var mean, deviation;
  192. mean = Math.round(new_sum / nums * 10) / 10 || 0;
  193. if (mean == Math.round(mean))
  194. mean += ".0";
  195. deviation = (new_sum - old_sum) / nums + parseFloat(old_dev) || 0;
  196. deviation = Math.round(deviation * 100) / 100;
  197. elem.text(elem.text()
  198. .replace("Score: " + old_score, "Score: " + mean)
  199. .replace("Dev.: " + old_dev, "Dev.: " + deviation));
  200. }
  201. function update_list_stats() {
  202. var old_sum_all = 0, new_sum_all = 0, nums_all = 0;
  203. $(".category_totals").each(function(i, totals) {
  204. var group = $(totals).closest("table").prev();
  205. var old_sum = 0, new_sum = 0, nums = 0;
  206. group.find("span[id^='scoreval']").each(function(j, elem) {
  207. if ($(elem).text() != "-") {
  208. old_sum += parseFloat($(elem).next().text());
  209. new_sum += parseFloat($(elem).text());
  210. nums++;
  211. }
  212. });
  213. apply_stats($(totals), old_sum, new_sum, nums);
  214. old_sum_all += old_sum;
  215. new_sum_all += new_sum;
  216. nums_all += nums;
  217. });
  218. if ($("#grand_totals").length > 0)
  219. apply_stats($("#grand_totals"), old_sum_all, new_sum_all, nums_all);
  220. }
  221. /* ---------------------------- Extension hooks ---------------------------- */
  222. function hook_list() {
  223. retrieve_scores(null, function(data) {
  224. $("span[id^='scoreval']").each(function(i, elem) {
  225. var anime_id = elem.id.split("scoreval")[1];
  226. var bucket_id = (parseInt(anime_id) % MAX_BUCKETS).toString();
  227. var bucket = data[bucket_id];
  228. $(elem).after($("<span>")
  229. .css("display", "none").text($(elem).text()));
  230. if (bucket !== undefined && bucket[anime_id] !== undefined)
  231. $(elem).text(bucket[anime_id] == 0 ? "-" : bucket[anime_id]);
  232. else {
  233. var current = parseInt($(elem).text());
  234. if (!isNaN(current))
  235. $(elem).text(current + ".0");
  236. }
  237. $("#scorediv" + anime_id)
  238. .after($("<div>")
  239. .attr("id", "scorediv" + anime_id)
  240. .css("display", "none")
  241. .append($('<input>')
  242. .attr("type", "text")
  243. .attr("id", "scoretext" + anime_id)
  244. .attr("size", "2")
  245. .keydown(function(a_id) {
  246. return function(ev) {
  247. if ((window.event ? window.event.keyCode : ev.which) == 13)
  248. update_list_score(a_id);
  249. else
  250. return true;
  251. }
  252. }(anime_id)))
  253. .append($("<input>")
  254. .attr("type", "button")
  255. .attr("id", "scorebutton" + anime_id)
  256. .attr("value", "Go")
  257. .click(function(a_id) {
  258. return function() { return update_list_score(a_id); }
  259. }(anime_id))))
  260. .remove();
  261. });
  262. prepare_list();
  263. if (should_sort)
  264. sort_list();
  265. update_list_stats();
  266. });
  267. }
  268. function hook_anime(anime_id) {
  269. retrieve_scores(anime_id, function(score) {
  270. var old_input = $("#myinfo_score");
  271. var old_button = $("input[name='myinfo_submit']");
  272. var is_new = old_button.attr("value") == "Add";
  273. if (!is_new && score === null)
  274. score = parseInt(old_input.val()) + ".0";
  275. old_input.after($("<span> / 10.0</span>"))
  276. .after($("<input>")
  277. .attr("type", "text")
  278. .attr("id", "myinfo_score")
  279. .attr("name", "myinfo_score")
  280. .attr("class", "inputtext")
  281. .attr("value", (score === null || score == 0) ? "" : score)
  282. .attr("size", "3"))
  283. .remove();
  284. old_button.after($("<input>")
  285. .attr("type", "button")
  286. .attr("name", "myinfo_submit")
  287. .attr("value", old_button.attr("value"))
  288. .attr("class", "inputButton")
  289. .click(function(a_id, is_new) {
  290. return function() { return update_anime_score(a_id, is_new); }
  291. }(anime_id, is_new)))
  292. .remove();
  293. });
  294. }
  295. function hook_add() {
  296. var old_input = $("select[name='score']");
  297. var old_submit = $("input[type='button'][onclick='checkValidSubmit(1)']");
  298. old_input.after($("<span> / 10.0</span>"))
  299. .after($("<input>")
  300. .attr("type", "text")
  301. .attr("id", "score_input")
  302. .attr("class", "inputtext")
  303. .attr("size", "3"))
  304. .hide();
  305. old_submit.after($("<input>")
  306. .attr("type", "button")
  307. .attr("class", "inputButton")
  308. .attr("style", old_submit.attr("style"))
  309. .attr("value", old_submit.attr("value"))
  310. .click(function(button) {
  311. return function() { return submit_add_form(button); }
  312. }(old_submit)))
  313. .hide();
  314. }
  315. function hook_edit(anime_id) {
  316. retrieve_scores(anime_id, function(score) {
  317. var old_input = $("select[name='score']");
  318. var old_edit = $("input[type='button'][onclick='checkValidSubmit(2)']");
  319. var old_delete = $("input[type='button'][onclick='checkValidSubmit(3)']");
  320. if (score === null)
  321. score = parseInt(old_input.val()) + ".0";
  322. old_input.after($("<span> / 10.0</span>"))
  323. .after($("<input>")
  324. .attr("type", "text")
  325. .attr("id", "score_input")
  326. .attr("class", "inputtext")
  327. .attr("value", score == 0 ? "" : score)
  328. .attr("size", "3"))
  329. .hide();
  330. old_edit.after($("<input>")
  331. .attr("type", "button")
  332. .attr("class", "inputButton")
  333. .attr("style", old_edit.attr("style"))
  334. .attr("value", old_edit.attr("value"))
  335. .click(function(a_id, button) {
  336. return function() { return submit_edit_form(a_id, 2, button); }
  337. }(anime_id, old_edit)))
  338. .hide();
  339. old_delete.after($("<input>")
  340. .attr("type", "button")
  341. .attr("class", "inputButton")
  342. .attr("value", old_delete.attr("value"))
  343. .click(function(a_id, button) {
  344. return function() { return submit_edit_form(a_id, 3, button); }
  345. }(anime_id, old_delete)))
  346. .hide();
  347. });
  348. }
  349. function hook_addtolist() {
  350. /* TODO: this entry point is unimplemented - it's rarely used and difficult
  351. to inject into, so I'm avoiding it for now. */
  352. $("<p><b>Note:</b> For the time being, anime added through this " +
  353. "interface cannot be given scores on the 10.0-point scale (the old " +
  354. "10-point system is used).</p><p>To give a more specific number, " +
  355. "simply add the anime here, then go to its own page or to your list " +
  356. "page, and update the score.</p>").insertAfter($("#stype").parent());
  357. }
  358. /* ------------------------------- Main hook ------------------------------- */
  359. $(document).ready(function() {
  360. var href = window.location.href;
  361. if (href.indexOf("/animelist/") != -1)
  362. hook_list();
  363. else if (href.indexOf("/anime/") != -1 || href.indexOf("/anime.php") != -1)
  364. hook_anime(get_anime_id_from_href(href));
  365. else if (href.indexOf("/panel.php") != -1 && href.indexOf("go=add") != -1)
  366. hook_add();
  367. else if (href.indexOf("/editlist.php") != -1 && href.indexOf("type=anime") != -1)
  368. hook_edit(get_edit_id_from_href(href));
  369. else if (href.indexOf("/addtolist.php") != -1)
  370. hook_addtolist();
  371. });