Anilist: Hide Uncommented Activity

Hides uncommented/unliked activity on Anilist's activity feeds

当前为 2023-09-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Anilist: Hide Uncommented Activity
  3. // @namespace https://github.com/SeyTi01/
  4. // @version 1.4
  5. // @description Hides uncommented/unliked activity on Anilist's activity feeds
  6. // @author SeyTi01
  7. // @match https://anilist.co/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const config = {
  16. removeUncommented: true,
  17. removeUnliked: false,
  18. targetLoadCount: 2
  19. };
  20.  
  21. const SELECTORS = {
  22. button: 'div.load-more',
  23. activity: 'div.activity-entry',
  24. replies: 'div.action.replies',
  25. likes: 'div.action.likes',
  26. };
  27.  
  28. const observer = new MutationObserver(observeMutations);
  29. let currentLoadCount = 0;
  30. let userPressedButton = true;
  31. let loadMoreButton;
  32. let cancelButton;
  33.  
  34. validateConfig(config);
  35. observer.observe(document.body, {childList: true, subtree: true});
  36.  
  37. function observeMutations(mutations) {
  38. for (const mutation of mutations) {
  39. if (mutation.addedNodes.length > 0) {
  40. mutation.addedNodes.forEach(handleAddedNode);
  41. }
  42. }
  43.  
  44. if (currentLoadCount < config.targetLoadCount && userPressedButton) {
  45. clickLoadMoreButton();
  46. } else {
  47. resetState();
  48. }
  49. }
  50.  
  51. function handleAddedNode(node) {
  52. if (!(node instanceof HTMLElement)) {
  53. return;
  54. }
  55.  
  56. if (node.matches(SELECTORS.activity)) {
  57. if (!removeEntry(node)) {
  58. currentLoadCount++;
  59. }
  60.  
  61. } else if (node.matches(SELECTORS.button)) {
  62. handleLoadMoreButton(node);
  63. }
  64. }
  65.  
  66. function removeEntry(node) {
  67. let removed = false;
  68. const repliesDiv = node.querySelector(SELECTORS.replies);
  69. const likesDiv = node.querySelector(SELECTORS.likes);
  70.  
  71. if ((config.removeUncommented && !hasCountSpan(repliesDiv)) || (config.removeUnliked && !hasCountSpan(likesDiv))) {
  72. node.remove();
  73. removed = true;
  74. }
  75.  
  76. return removed;
  77. }
  78.  
  79. function handleLoadMoreButton(button) {
  80. loadMoreButton = button;
  81. loadMoreButton.addEventListener('click', function() {
  82. userPressedButton = true;
  83. simulateDomEvents();
  84. showCancelButton();
  85. });
  86. }
  87.  
  88. function showCancelButton() {
  89. if (!cancelButton) {
  90. createCancelButton();
  91. } else {
  92. cancelButton.style.display = 'block';
  93. }
  94. }
  95.  
  96. function hasCountSpan(node) {
  97. return node?.querySelector('span.count');
  98. }
  99.  
  100. function simulateDomEvents() {
  101. const domEvent = new Event('scroll', {bubbles: true});
  102. const intervalId = setInterval(function() {
  103. if (userPressedButton) {
  104. window.dispatchEvent(domEvent);
  105. } else {
  106. clearInterval(intervalId);
  107. }
  108. }, 100);
  109. }
  110.  
  111. function clickLoadMoreButton() {
  112. if (loadMoreButton) {
  113. loadMoreButton.click();
  114. loadMoreButton = null;
  115. }
  116. }
  117.  
  118. function resetState() {
  119. currentLoadCount = 0;
  120. userPressedButton = false;
  121. cancelButton.style.display = 'none';
  122. }
  123.  
  124. function createCancelButton() {
  125. const buttonStyles = `
  126. position: fixed;
  127. bottom: 10px;
  128. right: 10px;
  129. z-index: 9999;
  130. line-height: 1.3;
  131. background-color: rgb(var(--color-background-blue-dark));
  132. color: rgb(var(--color-text-bright));
  133. font: 1.6rem 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
  134. -webkit-font-smoothing: antialiased;
  135. box-sizing: border-box;
  136. `;
  137.  
  138. cancelButton = Object.assign(document.createElement('button'), {
  139. textContent: 'Cancel',
  140. className: 'cancel-button',
  141. style: `--button-color: rgb(var(--color-blue)); ${buttonStyles}`,
  142. onclick: () => {
  143. userPressedButton = false;
  144. cancelButton.style.display = 'none';
  145. },
  146. });
  147.  
  148. document.body.appendChild(cancelButton);
  149. }
  150.  
  151. function validateConfig(config) {
  152. const errors = [
  153. typeof config.removeUncommented !== 'boolean' && 'removeUncommented must be a boolean',
  154. typeof config.removeUnliked !== 'boolean' && 'removeUnliked must be a boolean',
  155. (!Number.isInteger(config.targetLoadCount) || config.targetLoadCount < 1) && 'targetLoadCount must be a positive non-zero integer'
  156. ].filter(Boolean);
  157.  
  158. if (errors.length > 0) {
  159. throw new Error(errors.join('\n'));
  160. }
  161. }
  162. })();