A Chrome extension that gives you finer control over MyAnimeList.net scores
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

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