A Chrome extension that gives you finer control over MyAnimeList.net scores
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

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