Anilist Remover

Removes specific anime/manga from recommendations and results on Anilist

  1. // ==UserScript==
  2. // @name Anilist Remover
  3. // @name:ru Anilist скрытие
  4. // @namespace Kellen's userstyles
  5. // @author kln (t.me/kln_lzt)
  6. // @version 1.0
  7. // @description Removes specific anime/manga from recommendations and results on Anilist
  8. // @description:ru Возможность удалить выбранное аниме или мангу из рекомендаций и поиска на Anilist
  9. // @match https://anilist.co/*
  10. // @icon https://anilist.co/favicon.ico
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16. const anilistPatterns = {
  17. manga: /^https:\/\/anilist\.co\/manga\/.*/,
  18. anime: /^https:\/\/anilist\.co\/anime\/.*/,
  19. settings: /^https:\/\/anilist\.co\/settings\/media/
  20. };
  21. function getMediaUrl() {
  22. const urlPath = window.location.pathname;
  23. const urlParts = urlPath.split('/');
  24. const mediaType = urlParts[1];
  25. const mediaId = urlParts[2];
  26. const mediaTitle = urlParts[3];
  27. return `https://anilist.co/${mediaType}/${mediaId}/${mediaTitle}`;
  28. }
  29. function getMediaId() {
  30. const urlPath = window.location.pathname;
  31. const urlParts = urlPath.split('/');
  32. const mediaType = urlParts[1];
  33. const mediaId = urlParts[2];
  34. return `${mediaType}/${mediaId}`;
  35. }
  36. function getCookie(name) {
  37. const cookieName = `${name}=`;
  38. const decodedCookie = decodeURIComponent(document.cookie);
  39. const cookieArray = decodedCookie.split(';');
  40. for (let i = 0; i < cookieArray.length; i++) {
  41. let cookie = cookieArray[i].trim();
  42. if (cookie.indexOf(cookieName) === 0) {
  43. return cookie.substring(cookieName.length, cookie.length);
  44. }
  45. }
  46. return '';
  47. }
  48. function setCookie(name, value, days) {
  49. const expires = new Date();
  50. expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
  51. document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`;
  52. }
  53. function getRemovalList() {
  54. const removalListCookie = getCookie('removalList');
  55. return removalListCookie ? JSON.parse(removalListCookie) : [];
  56. }
  57. function addToRemovalList(mediaUrl) {
  58. const removalList = getRemovalList();
  59. if (!removalList.includes(mediaUrl)) {
  60. removalList.push(mediaUrl);
  61. setCookie('removalList', JSON.stringify(removalList), 365);
  62. }
  63. }
  64. function removeFromRemovalList(mediaUrl) {
  65. const removalList = getRemovalList();
  66. const index = removalList.indexOf(mediaUrl);
  67. if (index !== -1) {
  68. removalList.splice(index, 1);
  69. setCookie('removalList', JSON.stringify(removalList), 365);
  70. }
  71. }
  72. function toggleMediaRemoval() {
  73. const currentMediaUrl = getMediaUrl();
  74. const removalList = getRemovalList();
  75. if (removalList.includes(currentMediaUrl)) {
  76. removeFromRemovalList(currentMediaUrl);
  77. } else {
  78. addToRemovalList(currentMediaUrl);
  79. }
  80. updateRemovalButton();
  81. renderRemovalList(document.getElementById('removalList'));
  82. }
  83. // Функция для обновления текста кнопки удаления на основе статуса удаления текущего медиа
  84. function updateRemovalButton() {
  85. const currentMediaId = getMediaId();
  86. const removalList = getRemovalList();
  87. const isInRemovalList = removalList.some(url => url.includes(currentMediaId));
  88. const buttonText = isInRemovalList ? 'Show this anime' : 'Hide this anime';
  89. const removalButton = document.getElementById('removalButton');
  90. if (removalButton) {
  91. removalButton.textContent = buttonText;
  92. } else {
  93. console.error('Removal button not found!');
  94. }
  95. }
  96. function createRemovalSection() {
  97. const content = document.querySelector('.content');
  98. if (!content) return;
  99. const existingSection = document.getElementById('removalSection');
  100. if (existingSection) {
  101. existingSection.remove();
  102. }
  103. const removalSection = document.createElement('div');
  104. removalSection.id = 'removalSection';
  105. removalSection.classList.add('section');
  106. const heading = document.createElement('h2');
  107. heading.textContent = 'Removal List';
  108. removalSection.appendChild(heading);
  109. const removalList = document.createElement('div');
  110. removalList.id = 'removalList';
  111. removalSection.appendChild(removalList);
  112. content.appendChild(removalSection);
  113. renderRemovalList(removalList);
  114. }
  115. function renderRemovalList(removalList) {
  116. if (!removalList) return; // Добавлена проверка на null
  117. removalList.innerHTML = '';
  118. const removedMedia = getRemovalList();
  119. if (removedMedia.length === 0) {
  120. const noMediaMessage = document.createElement('div');
  121. noMediaMessage.textContent = 'No anime/manga in the removal list.';
  122. removalList.appendChild(noMediaMessage);
  123. return;
  124. }
  125. removedMedia.forEach(mediaUrl => {
  126. const mediaTitle = mediaUrl.split('/').pop();
  127. const mediaDiv = document.createElement('div');
  128. mediaDiv.classList.add('name');
  129. mediaDiv.style.display = 'flex';
  130. mediaDiv.style.flexDirection = 'row';
  131. mediaDiv.style.flexWrap = 'nowrap';
  132. mediaDiv.style.justifyContent = 'space-between';
  133. mediaDiv.style.alignItems = 'center';
  134. mediaDiv.style.alignContent = 'normal';
  135. mediaDiv.style.marginBottom = '20px';
  136. const mediaLink = document.createElement('a');
  137. mediaLink.href = mediaUrl;
  138. mediaLink.textContent = mediaTitle;
  139. const removeButton = document.createElement('div');
  140. removeButton.classList.add('button', 'danger');
  141. removeButton.style.margin = '0';
  142. removeButton.textContent = 'Remove';
  143. removeButton.addEventListener('click', () => {
  144. removeFromRemovalList(mediaUrl);
  145. renderRemovalList(removalList); // Pass the removalList element
  146. });
  147. mediaDiv.appendChild(mediaLink);
  148. mediaDiv.appendChild(removeButton);
  149. removalList.appendChild(mediaDiv);
  150. });
  151. }
  152. function isAnilistPage() {
  153. const currentUrl = window.location.href;
  154. return Object.values(anilistPatterns).some(pattern => pattern.test(currentUrl));
  155. }
  156. function createRemovalButton() {
  157. if (isAnilistPage()) {
  158. const existingButton = document.getElementById('removalButton');
  159. if (existingButton) {
  160. // Кнопка уже существует, ничего не делать
  161. return;
  162. }
  163. const dropdownMenu = document.querySelector('.el-dropdown-menu');
  164. const firstListItem = dropdownMenu?.querySelector('li');
  165. if (dropdownMenu && firstListItem) {
  166. const removalButton = document.createElement('li');
  167. removalButton.id = 'removalButton';
  168. removalButton.textContent = 'Checking Removal Status...';
  169. removalButton.classList.add('el-dropdown-menu__item');
  170. removalButton.addEventListener('click', toggleMediaRemoval);
  171. dropdownMenu.insertBefore(removalButton, firstListItem);
  172. updateRemovalButton();
  173. } else {
  174. // Попытаться создать кнопку позже
  175. setTimeout(createRemovalButton, 500);
  176. }
  177. }
  178. }
  179. function handlePageChange() {
  180. const currentUrl = window.location.href;
  181. const removalButton = document.getElementById('removalButton');
  182. const removalDropdown = document.getElementById('removalDropdown');
  183. const removalSection = document.getElementById('removalSection');
  184. if (anilistPatterns.manga.test(currentUrl) || anilistPatterns.anime.test(currentUrl)) {
  185. if (!removalButton) {
  186. createRemovalButton();
  187. }
  188. if (removalDropdown) {
  189. removalDropdown.remove();
  190. }
  191. if (removalSection) {
  192. removalSection.remove();
  193. }
  194. } else if (anilistPatterns.settings.test(currentUrl) && currentUrl.includes('/media')) {
  195. if (!removalButton) {
  196. createRemovalButton();
  197. }
  198. if (!removalSection) {
  199. createRemovalSection();
  200. }
  201. } else {
  202. if (removalButton) {
  203. removalButton.remove();
  204. }
  205. if (removalDropdown) {
  206. removalDropdown.remove();
  207. }
  208. if (removalSection) {
  209. removalSection.remove();
  210. }
  211. }
  212. }
  213. function observeDOMChanges() {
  214. const observer = new MutationObserver(function(mutations) {
  215. mutations.forEach(function(mutation) {
  216. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  217. handlePageChange();
  218. removeMedia();
  219. }
  220. });
  221. });
  222. observer.observe(document.body, { childList: true, subtree: true });
  223. }
  224. function removeMedia() {
  225. const removalList = getRemovalList();
  226. removalList.forEach(mediaUrl => {
  227. const mediaId = mediaUrl.split('/')[4];
  228. const mediaLinks = document.querySelectorAll(`a[href*="/anime/${mediaId}/"], a[href*="/manga/${mediaId}/"]`);
  229. mediaLinks.forEach(link => {
  230. const recommendationCard = link.closest('.recommendation-card');
  231. if (recommendationCard) {
  232. recommendationCard.remove();
  233. }
  234. const resultElement = link.closest('.result');
  235. if (resultElement) {
  236. resultElement.remove();
  237. }
  238. const mediaCard = link.closest('.media-card');
  239. if (mediaCard) {
  240. mediaCard.remove();
  241. }
  242. });
  243. });
  244. }
  245. window.addEventListener('load', function() {
  246. handlePageChange();
  247. observeDOMChanges();
  248. const currentUrl = window.location.href;
  249. if (anilistPatterns.settings.test(currentUrl)) {
  250. createRemovalSection();
  251. }
  252. });
  253. window.addEventListener('hashchange', handlePageChange);
  254. window.addEventListener('popstate', handlePageChange);
  255. removeMedia();
  256. })();
  257.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址