MAL Summary+Profile Stats With Percentages + Hours

See your profile stats in % + Hours, and see the Summary Stats in %

  1. // ==UserScript==
  2. // @name MAL Summary+Profile Stats With Percentages + Hours
  3. // @namespace http://tampermonkey.net/
  4. // @version 6
  5. // @description See your profile stats in % + Hours, and see the Summary Stats in %
  6. // @author Only_Brad (& commented by hacker09)
  7. // @include /^https:\/\/myanimelist\.net\/(anime|manga)\/[\d]+\/.*\/stats/
  8. // @match https://myanimelist.net/profile/*
  9. // @exclude https://myanimelist.net/*/*/clubs
  10. // @exclude https://myanimelist.net/*/*/reviews
  11. // @exclude https://myanimelist.net/*/*/friends
  12. // @exclude https://myanimelist.net/*/*/recommendations
  13. // @icon https://www.google.com/s2/favicons?domain=myanimelist.net
  14. // @run-at document-end
  15. // @grant GM_addStyle
  16. // ==/UserScript==
  17.  
  18. // Functions to select the days txt and day numbers to convert then to txt hours and numbers in hours later
  19. (function() {
  20. if (location.href.match('profile') !== null) //If the current url is the profile of some user
  21. { //Starts the if condition
  22. GM_addStyle(".profile .user-statistics .stats-status{width: 200px;}");
  23. document.head.insertAdjacentHTML('afterend', '<style>span.di-ib.fl-r.lh10 {position: absolute!important; text-indent: 10px!important;}</style>'); //Fixes new dark mode bug
  24. const days = document.querySelector(".di-tc.al.pl8.fs12.fw-b"),
  25. hours = days.cloneNode(true),
  26. hoursText = hours.querySelector("span"),
  27. hoursValueNode = hoursText.nextSibling,
  28. hoursValue = parseFloat(hoursValueNode.textContent.replace(/,/g, ""));
  29.  
  30. // Add the symbol // On the beginning the 2 lines below "disable" the total hours,then you can have just the mal percentages feature
  31. hoursText.textContent = "Hours: ";
  32. hoursValueNode.textContent = (hoursValue * 24).toFixed(1);
  33. days.insertAdjacentElement("afterend", hours);
  34.  
  35. // Functions to select all the animes stats and the Total stats
  36. const total = parseInt(document.querySelector(".stats-data.fl-r span:nth-child(2)").textContent.replace(/,/g, ""));
  37. const [watching, completed, onHold, dropped, planToWatch] = document.querySelectorAll(".di-ib.fl-r.lh10");
  38.  
  39. // Functions to add the percentage after the total number of each watching,completed,onHold,dropped,planToWatch
  40. [watching, completed, onHold, dropped, planToWatch].forEach(addPercentage);
  41.  
  42. // Functions that do the math
  43. function getPercentage(node) {
  44. const value = parseInt(node.textContent.replace(/,/g, ""));
  45. if (total === 0) return "0.00";
  46. return (value * 100 / total).toFixed(2);
  47. }
  48.  
  49. // Functions to show and append the scores after each watching,completed,onHold,dropped,planToWatch
  50. function addPercentage(node) {
  51. const percentage = getPercentage(node);
  52. node.textContent = `${node.textContent} (${percentage}%)`;
  53. }
  54. } //Finishes the if condition
  55. else //If the user is on the summary page
  56. { //Starts the else condition
  57. const [, , , watching, completed, onHold, dropped, planToWatch, total] = document.querySelectorAll("#content > table > tbody > tr > td:nth-child(2) > div.js-scrollfix-bottom-rel > div");
  58. const totalValue = getValue(total);
  59.  
  60. [watching, completed, onHold, dropped, planToWatch].forEach(addPercentage);
  61.  
  62. //Uncomment the below code to swap "Watching" and "Completed" positions
  63. //watching.before(completed);
  64.  
  65. function getValue(node) {
  66. const text = node.querySelector("span");
  67. return parseInt(text.nextSibling.textContent.replace(/,/g, ""));
  68. }
  69.  
  70. function getPercentage(node) {
  71. if (totalValue === 0) return "0.00";
  72. const value = getValue(node);
  73. return (value * 100 / totalValue).toFixed(2);
  74. }
  75.  
  76. function addPercentage(node) {
  77. const text = node.querySelector("span");
  78. const valueNode = text.nextSibling;
  79. const percentage = getPercentage(node);
  80. valueNode.textContent = `${valueNode.textContent} (${percentage}%)`;
  81. addBar(node, percentage);
  82. }
  83.  
  84. function addBar(node, percentage) {
  85. const percentageText = convertTextNodeToSpan(node);
  86. percentageText.style = "font-weight: normal";
  87.  
  88. const textNode = node.querySelector(".dark_text");
  89. textNode.appendChild(percentageText);
  90.  
  91. const bar = document.createElement("div");
  92. bar.setAttribute("class", "updatesBar");
  93. bar.style = `display: block; height: 15px; width: ${percentage}%;`;
  94.  
  95. textNode.after(bar);
  96. }
  97.  
  98. function convertTextNodeToSpan(node) {
  99. for (const child of node.childNodes) {
  100. if (child.nodeType === Node.TEXT_NODE) {
  101. const text = child.textContent;
  102. child.remove();
  103. const span = document.createElement("span");
  104. span.textContent = text;
  105. node.appendChild(span);
  106. return span;
  107. }
  108. }
  109. }
  110. } //Finishes the else condition
  111. })()