KG_Hide_Spam_Races

This script will hide all the races what are created for bad purposes

  1. // ==UserScript==
  2. // @name KG_Hide_Spam_Races
  3. // @namespace http://klavogonki.ru
  4. // @version 0.7
  5. // @description This script will hide all the races what are created for bad purposes
  6. // @author Patcher
  7. // @match *://klavogonki.ru/gamelist/
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=klavogonki.ru
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // Constants for the threshold, time limits, and max hidden elements
  13. const itemThreshold = 1; // Number of items
  14. const timeLimitInSeconds = 3; // Time in seconds
  15. const maxHiddenElements = 300; // Maximum number of hidden elements before removal
  16. // Global variable to store the user id what is exceeded the limit
  17. let currentUserId;
  18.  
  19. // Exposed settings for info element svg icons
  20. const xmlns = "http://www.w3.org/2000/svg";
  21. const svgSize = 16;
  22. const svgStrokeWidth = 2;
  23.  
  24. const eyeSvgOn = `
  25. <svg xmlns="${xmlns}" width="${svgSize}" height="${svgSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor"
  26. stroke-width="${svgStrokeWidth}" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-on">
  27. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  28. <circle cx="12" cy="12" r="3"></circle>
  29. </svg>
  30. `;
  31.  
  32. const eyeSvgOff = `
  33. <svg xmlns="${xmlns}" width="${svgSize}" height="${svgSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor"
  34. stroke-width="${svgStrokeWidth}" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off">
  35. <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20 c-7 0-11-8-11-8 a18.45 18.45 0 0 1 5.06-5.94 M9.9 4.24A9.12 9.12 0 0 1 12 4
  36. c7 0 11 8 11 8 a18.5 18.5 0 0 1-2.16 3.19 m-6.72-1.07 a3 3 0 1 1-4.24-4.24 L17.94 17.94z">
  37. </path>
  38. <line x1="1" y1="1" x2="23" y2="23"></line>
  39. </svg>
  40. `;
  41.  
  42. // Helper function to toggle visibility icon and update item info visibility state
  43. function toggleItemInfoVisibility() {
  44. let visibilityButton = raceItem.querySelector('.visibility-button');
  45. let raceItemNickname = raceItem.querySelector('.race-item-nickname');
  46.  
  47. if (visibilityButton && raceItemNickname) {
  48. // Check the current state in localStorage
  49. const isVisible = localStorage.getItem('raceSpamNicknameVisibility') === 'visible';
  50.  
  51. // Toggle the icon and update the localStorage state
  52. if (isVisible) {
  53. visibilityButton.innerHTML = eyeSvgOff;
  54. localStorage.setItem('raceSpamNicknameVisibility', 'hidden');
  55. // Set display to 'none' to hide the race item nickname
  56. raceItemNickname.style.display = 'none';
  57. } else {
  58. visibilityButton.innerHTML = eyeSvgOn;
  59. localStorage.setItem('raceSpamNicknameVisibility', 'visible');
  60. // Set display to 'inline-flex' to show the race item nickname
  61. raceItemNickname.style.display = 'inline-flex';
  62. }
  63. }
  64. }
  65.  
  66. // Create a single raceItem element if it doesn't exist
  67. let raceItem = document.querySelector('.race-item');
  68. if (!raceItem) {
  69. raceItem = document.createElement('div');
  70. raceItem.classList.add('race-item');
  71. document.body.appendChild(raceItem);
  72.  
  73. }
  74.  
  75. // Function to update the raceItem element
  76. function updateRaceItem(profileText, exceededLimit) {
  77. if (raceItem) {
  78. // Check if the localStorage key exists, if not, create it with default value 'visible'
  79. if (!localStorage.getItem('raceSpamNicknameVisibility')) {
  80. localStorage.setItem('raceSpamNicknameVisibility', 'visible');
  81. }
  82.  
  83. // Set the current state in localStorage
  84. const isVisible = localStorage.getItem('raceSpamNicknameVisibility') === 'visible';
  85.  
  86. // Update the classes and background color based on whether it's removed or saved
  87. raceItem.classList.toggle('removed-spam-races', exceededLimit);
  88. raceItem.classList.toggle('saved-normal-races', !exceededLimit);
  89.  
  90. // Create the visibilityButton SVG element only if it doesn't exist
  91. let visibilityButton = raceItem.querySelector('.visibility-button');
  92. if (!visibilityButton) {
  93. visibilityButton = document.createElement('div');
  94. visibilityButton.classList.add('visibility-button');
  95.  
  96. // Set the initial visibility icon based on the localStorage value
  97. visibilityButton.innerHTML = isVisible ? eyeSvgOn : eyeSvgOff;
  98.  
  99. raceItem.appendChild(visibilityButton);
  100.  
  101. // Set up click event to toggle visibility icon and item info visibility
  102. visibilityButton.addEventListener('click', toggleItemInfoVisibility);
  103. }
  104.  
  105. // Set the text content for profileText
  106. let raceItemNickname = raceItem.querySelector('.race-item-nickname');
  107. if (!raceItemNickname) {
  108. raceItemNickname = document.createElement('div');
  109. raceItemNickname.classList.add('race-item-nickname');
  110.  
  111. // Set the display property based on the visibility state
  112. raceItemNickname.style.display = isVisible ? 'inline-flex' : 'none';
  113.  
  114. raceItem.appendChild(raceItemNickname);
  115. }
  116.  
  117. // Update text content of nickname if already created
  118. if (raceItemNickname) {
  119. raceItemNickname.textContent = profileText;
  120.  
  121. // Add or remove click event listener to/from raceItemNickname based on exceededLimit
  122. if (exceededLimit) {
  123. raceItemNickname.addEventListener('click', navigateToProfile);
  124. } else {
  125. raceItemNickname.removeEventListener('click', navigateToProfile);
  126. }
  127. }
  128. }
  129. }
  130.  
  131. // Function to navigate to the user's profile
  132. function navigateToProfile() {
  133. if (currentUserId) {
  134. const profileUrl = `https://klavogonki.ru/profile/${currentUserId}`;
  135. window.open(profileUrl, '_blank');
  136. }
  137. }
  138.  
  139. // Create an object to store the count and timestamp of each profile text
  140. const profileTextCount = {};
  141.  
  142. // Function to process a single .item element
  143. function processItem(item) {
  144. const profileElements = item.querySelectorAll('[id^="player_name"]');
  145. if (profileElements.length > 0) {
  146. const profileTextArray = Array.from(profileElements).map(element => element.textContent.trim());
  147.  
  148. // Check if the array contains a valid profile text at index 0
  149. if (profileTextArray[0] && profileTextArray[0].trim() !== '') {
  150. const profileText = profileTextArray[0]; // Get the profile text at index 0
  151.  
  152. if (!profileTextCount[profileText]) {
  153. profileTextCount[profileText] = {
  154. count: 0,
  155. lastTimestamp: 0,
  156. exceededLimit: false,
  157. };
  158. }
  159.  
  160. const currentTime = Date.now();
  161.  
  162. // Check if the user at index 0 has created more than itemThreshold items with the same profile text within the time limit
  163. if (profileTextCount[profileText].count >= itemThreshold && currentTime - profileTextCount[profileText].lastTimestamp <= timeLimitInSeconds * 1000) {
  164. // Set a flag to indicate that this user has exceeded the limit
  165. profileTextCount[profileText].exceededLimit = true;
  166.  
  167. // Hide the item
  168. item.style.display = 'none';
  169.  
  170. // Get the anchor element within the profileElements context
  171. const anchorElement = profileElements[0].querySelector('.profile');
  172.  
  173. // Check if the anchor element exists
  174. if (anchorElement) {
  175. // Get the user ID and user NAME
  176. const userIdMatch = anchorElement.getAttribute('href').match(/\/profile\/(\d+)\//);
  177. const userId = userIdMatch ? userIdMatch[1] : null;
  178.  
  179. // Update the globally stored currentUserId
  180. currentUserId = userId;
  181. }
  182. }
  183.  
  184. // Update the raceItem element
  185. updateRaceItem(profileText, profileTextCount[profileText].exceededLimit);
  186.  
  187. // Update the count and timestamp for this profile text in the object
  188. profileTextCount[profileText].count += 1;
  189. profileTextCount[profileText].lastTimestamp = currentTime;
  190. }
  191. }
  192. }
  193.  
  194. // Function to hide all items of a user who exceeded the limit with a delay
  195. function hideItemsWithExceededLimit(profileText) {
  196. document.querySelectorAll('#gamelist .item').forEach(itemToHide => {
  197. const itemProfileElements = itemToHide.querySelectorAll('[id^="player_name"]');
  198. const profileTextArray = Array.from(itemProfileElements).map(element => element.textContent.trim());
  199. if (profileTextArray.length > 0 && profileTextArray[0] === profileText) {
  200. // Check if the item still exists in the DOM before attempting to hide it
  201. if (itemToHide.parentNode) {
  202. // Hide the item by setting display to "none"
  203. itemToHide.style.display = 'none';
  204.  
  205. // Clear the user's data after hiding
  206. profileTextArray.forEach(profileText => {
  207. delete profileTextCount[profileText];
  208. });
  209.  
  210. // Check if the number of hidden elements exceeds maxHiddenElements
  211. const hiddenElements = document.querySelectorAll('#gamelist .item[style*="display: none;"]');
  212. if (hiddenElements.length > maxHiddenElements) {
  213. // Remove excess hidden elements and their comments
  214. hiddenElements.forEach(hiddenElement => {
  215. const nextSibling = hiddenElement.nextSibling;
  216. if (nextSibling && nextSibling.nodeType === Node.COMMENT_NODE) {
  217. nextSibling.parentNode.removeChild(nextSibling);
  218. }
  219. hiddenElement.parentNode.removeChild(hiddenElement);
  220. });
  221. }
  222. }
  223. }
  224. });
  225. }
  226.  
  227. // Iterate through existing .item elements
  228. document.querySelectorAll('#gamelist .item').forEach(item => {
  229. processItem(item);
  230. });
  231.  
  232. // Create a MutationObserver to watch for changes in #gamelist
  233. const observer = new MutationObserver(mutationsList => {
  234. mutationsList.forEach(mutation => {
  235. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  236. // Iterate through newly added .item elements
  237. mutation.addedNodes.forEach(addedNode => {
  238. if (addedNode.nodeType === 1 && addedNode.classList.contains('item')) {
  239. processItem(addedNode);
  240. }
  241. });
  242. }
  243. });
  244.  
  245. // Check for users who exceeded the limit and hide their items
  246. Object.keys(profileTextCount).forEach(profileText => {
  247. if (profileTextCount.hasOwnProperty(profileText) && profileTextCount[profileText].exceededLimit) {
  248. hideItemsWithExceededLimit(profileText);
  249. }
  250. });
  251. });
  252.  
  253. // Start observing #gamelist for changes
  254. const gamelist = document.getElementById('gamelist');
  255. observer.observe(gamelist, { childList: true, subtree: true });
  256.  
  257. // Function to inject the CSS style with the removed Race Item Animation into the <head> tag
  258. function injectCSS() {
  259. const hideSpamRacesStyles = document.createElement('style');
  260. // Add the class 'hide-spam-races' to the <style> tag
  261. hideSpamRacesStyles.classList.add('hide-spam-races');
  262.  
  263. hideSpamRacesStyles.innerHTML = `
  264. .race-item {
  265. display: flex;
  266. justify-content: space-around';
  267. align-items: center;
  268. position: fixed;
  269. bottom: 6px;
  270. left: 6px;
  271. }
  272.  
  273. .visibility-button {
  274. padding: 6px;
  275. cursor: pointer;
  276. transition: filter 0.3s;
  277. }
  278.  
  279. .visibility-button:hover {
  280. filter: brightness(1.4);
  281. }
  282.  
  283. .race-item-nickname {
  284. padding: 6px;
  285. }
  286.  
  287. @keyframes removedRaceItemAnimation {
  288. 0%, 20%, 50%, 80%, 100% {
  289. transform: translateY(0);
  290. }
  291. 40% {
  292. transform: translateY(-30px);
  293. }
  294. 60% {
  295. transform: translateY(-15px);
  296. }
  297. }
  298.  
  299. .removed-spam-races {
  300. background-color: hsl(0, 50%, 15%);
  301. color: hsl(0, 50%, 70%);
  302. border: 1px solid hsl(0, 50%, 40%) !important;
  303. cursor: pointer;
  304. animation: removedRaceItemAnimation 1s ease-in-out;
  305. }
  306.  
  307. .removed-spam-races .race-item-nickname {
  308. transition: filter 0.3s;
  309. }
  310.  
  311. .removed-spam-races .race-item-nickname:hover {
  312. filter: brightness(1.4);
  313. }
  314.  
  315. .saved-normal-races {
  316. background-color: hsl(100, 50%, 10%);
  317. color: hsl(100, 50%, 50%);
  318. border: 1px solid hsl(100, 50%, 25%) !important;
  319. cursor: default;
  320. }
  321. `;
  322.  
  323.  
  324. // Append the style element to the head
  325. document.head.appendChild(hideSpamRacesStyles);
  326. }
  327.  
  328. // Call the injectCSS function to inject the CSS
  329. injectCSS();

QingJ © 2025

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