A Chrome extension that gives you finer control over MyAnimeList.net scores
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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