🏠 Home 

MAL (MyAnimeList) - Accurate Progress & Sorting On Anime List

Accurately sorts your anime list by progress, and displays correct progress percentages for each anime


Install this script?
  1. // ==UserScript==
  2. // @name MAL (MyAnimeList) - Accurate Progress & Sorting On Anime List
  3. // @version 3.2
  4. // @author Ezektor
  5. // @description Accurately sorts your anime list by progress, and displays correct progress percentages for each anime
  6. // @match https://myanimelist.net/animelist/*
  7. // @grant none
  8. // @icon https://myanimelist.net/favicon.ico
  9. // @namespace https://greasyfork.org/users/1414348
  10. // ==/UserScript==
  11. (function() {
  12. 'use strict';
  13. let isSortingEnabled = false;
  14. let originalRows = [];
  15. let sortDirection = 'desc';
  16. const toggleButton = document.createElement('button');
  17. toggleButton.innerHTML = '➖ Default';
  18. toggleButton.style.position = 'fixed';
  19. toggleButton.style.bottom = '20px';
  20. toggleButton.style.right = '20px';
  21. toggleButton.style.padding = '10px';
  22. toggleButton.style.backgroundColor = '#9e9e9e';
  23. toggleButton.style.color = 'white';
  24. toggleButton.style.border = 'none';
  25. toggleButton.style.borderRadius = '5px';
  26. toggleButton.style.cursor = 'pointer';
  27. toggleButton.style.zIndex = '9999';
  28. document.body.appendChild(toggleButton);
  29. function isAllAnimePage() {
  30. return window.location.href.includes('/animelist/') && window.location.href.includes('?status=7');
  31. }
  32. function calculateProgress(row) {
  33. const progressCell = row.querySelector('.data.progress');
  34. if (!progressCell) return 0;
  35. const progressText = progressCell.innerText.trim();
  36. const [watched, total] = progressText.replace(/\[[^\]]*\]/g, '').split('/').map(s => parseInt(s.trim(), 10) || 0);
  37. return total == null ? 100 : total === 0 ? 0 : Math.floor((watched / total) * 100);
  38. }
  39. function addProgressPercentage(row) {
  40. const progressCell = row.querySelector('.data.progress');
  41. if (!progressCell) return;
  42. const progressText = progressCell.innerText.trim();
  43. if (/^\d+$/.test(progressText)) return;
  44. const existingPercentage = progressCell.querySelector('.progress-percentage')
  45. if (existingPercentage) {
  46. existingPercentage.remove();
  47. }
  48. const progressPercentage = calculateProgress(row);
  49. const progressPercentageElement = document.createElement('div');
  50. progressPercentageElement.style.fontSize = '12px';
  51. progressPercentageElement.style.marginTop = '5px';
  52. progressPercentageElement.classList.add('progress-percentage');
  53. progressPercentageElement.textContent = `${progressPercentage}% progress`;
  54. if (progressPercentage <= 33) {
  55. progressPercentageElement.style.color = '#f44336';
  56. } else if (progressPercentage <= 66) {
  57. progressPercentageElement.style.color = '#ff9800';
  58. } else {
  59. progressPercentageElement.style.color = '#4caf50';
  60. }
  61. progressCell.appendChild(progressPercentageElement);
  62. }
  63. function sortRowsByProgress(rows, direction) {
  64. return [...rows].sort((a, b) => {
  65. const progressA = calculateProgress(a);
  66. const progressB = calculateProgress(b);
  67. const isCompletedA = a.querySelector('.data.status').classList.contains('completed');
  68. const isCompletedB = b.querySelector('.data.status').classList.contains('completed');
  69. const isDroppedA = a.querySelector('.data.status').classList.contains('dropped');
  70. const isDroppedB = b.querySelector('.data.status').classList.contains('dropped');
  71. if (isAllAnimePage()) {
  72. if (isDroppedA && !isDroppedB) return 1;
  73. if (!isDroppedA && isDroppedB) return -1;
  74. if (isCompletedA && !isCompletedB) return direction === 'desc' ? -1 : 1;
  75. if (!isCompletedA && isCompletedB) return direction === 'desc' ? 1 : -1;
  76. }
  77. return direction === 'asc'
  78. ? progressA - progressB
  79. : progressB - progressA;
  80. });
  81. }
  82. function updateTable(sortedRows) {
  83. const table = document.querySelector('table');
  84. const tbody = table.querySelector('tbody.list-item');
  85. tbody.innerHTML = '';
  86. sortedRows.forEach(row => tbody.appendChild(row));
  87. }
  88. function restoreOriginalOrder() {
  89. const table = document.querySelector('table');
  90. const tbody = table.querySelector('tbody.list-item');
  91. tbody.innerHTML = '';
  92. originalRows.forEach(row => tbody.appendChild(row));
  93. }
  94. function storeOriginalRows() {
  95. const rows = document.querySelectorAll('tbody.list-item .list-table-data');
  96. originalRows = [...rows];
  97. }
  98. function main() {
  99. const rows = document.querySelectorAll('tbody.list-item .list-table-data');
  100. rows.forEach(addProgressPercentage);
  101. if (isSortingEnabled) {
  102. const sortedRows = sortRowsByProgress(rows, sortDirection);
  103. updateTable(sortedRows);
  104. }
  105. }
  106. toggleButton.addEventListener('click', () => {
  107. if (!isSortingEnabled) {
  108. isSortingEnabled = true;
  109. sortDirection = 'desc';
  110. toggleButton.style.backgroundColor = '#4CAF50';
  111. toggleButton.innerHTML = 'Sorting In: 📉 Descending Order';
  112. main();
  113. } else if (sortDirection === 'desc') {
  114. sortDirection = 'asc';
  115. toggleButton.innerHTML = 'Sorting In: 📈 Ascending Order';
  116. main();
  117. } else {
  118. isSortingEnabled = false;
  119. toggleButton.style.backgroundColor = '#9e9e9e';
  120. toggleButton.innerHTML = '➖ Default';
  121. restoreOriginalOrder();
  122. }
  123. });
  124. window.addEventListener('load', () => {
  125. storeOriginalRows();
  126. main();
  127. });
  128. })();