Curse detector

Detect curses on MZ forum posts. Works with Spanish words only.

  1. // ==UserScript==
  2. // @name Curse detector
  3. // @namespace mz-curse-detector
  4. // @description Detect curses on MZ forum posts. Works with Spanish words only.
  5. // @description:es Detectar groserías en los mensajes de los foros de MZ. Solamente detecta palabras en español.
  6. // @homepage https://github.com/rhonaldomaster/mz-curse-detector
  7. // @icon https://www.managerzone.com/favicon.ico?v2
  8. // @include https://*managerzone.*p=forum&sub=topic*
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_xmlhttpRequest
  12. // @version 0.4
  13. // @copyright GNU/GPL v3
  14. // @author rhonaldomaster
  15. // @license GPL-3.0-or-later
  16. // @compatible chrome
  17. // @compatible firefox
  18. // @compatible opera
  19. // @compatible safari
  20. // @compatible edge
  21. // ==/UserScript==
  22.  
  23.  
  24. // Default words in case the fetch fails
  25. const DEFAULT_CURSE_WORDS = [
  26. 'boba', 'bobo', 'boluda', 'bolu.da', 'boludo', 'bolu.do', 'bosta',
  27. 'bostera', 'bostero', 'burrazo', 'burro', 'cometrava', 'cometraba',
  28. 'concha', ' culo', 'estupida', 'estúpida', 'estupido', 'estúpido',
  29. 'forra', 'forro', 'gil', 'gilipolla', 'gonorrea', 'hdp', 'hipocrita',
  30. 'hipócrita', 'hijo de puta', 'hitler', 'idiota', 'imbecil', 'imbécil',
  31. 'kkk', 'kuka', 'lacra', 'la tenés adentro', 'la tenes adentro',
  32. 'la tenes bien adentro', 'la tenés bien adentro', 'la tenéis bien adentro',
  33. ' lta', 'malcogida', 'malcogido', 'mal cogida', 'mal cogido', 'malparida',
  34. 'malparido', 'mal parida', 'mal parido', 'marica', 'marmota', 'mediocre',
  35. 'mierda', 'miserable', 'mogolico', 'mogólico', 'montonero', 'mu ',
  36. 'muerde almohada', 'muerdealmohada', 'negro cabeza', 'patetico', 'patético',
  37. 'payasa', 'payaso', 'pelotuda', 'pelotudo', 'pene', 'perra', 'puta',
  38. 'putear', 'puto', 'retrasada', 'retrasado', 'ridicula', 'ridícula',
  39. 'ridiculo', 'ridículo', 'salame', 'sorete', 'sucio', 'subnormal',
  40. 'tarada', 'tarado', 'tonta', 'tontaza', 'tontazo', 'tonto', 'trampa',
  41. 'tramposa', 'tramposo', 'vende ajo', 'vendo ajo', 'verduler'
  42. ];
  43.  
  44. // Cache configuration
  45. const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
  46. const CACHE_KEY = 'curseWordsCache';
  47. const CACHE_TIMESTAMP = 'curseWordsTimestamp';
  48. const CACHE_VERSION = '1.0.0';
  49.  
  50. // GitHub Pages URL where the JSON is hosted
  51. const WORDS_JSON_URL = 'https://rhonaldomaster.github.io/mz-curse-detector/curse-words.json';
  52.  
  53. // Store the current list of curse words
  54. let curseWords = [...DEFAULT_CURSE_WORDS];
  55.  
  56. function searchAndHighlightWord(word, textContainer) {
  57. const warningColor = 'var(--curseColor)';
  58. const originalText = textContainer.innerHTML;
  59. if (originalText.indexOf(`<span style="color:${warningColor};font-weight:bold;text-decoration:underline;">`) > -1) {
  60. return;
  61. }
  62.  
  63. const regex = new RegExp(`\\b${word}\\b`, 'gi');
  64. const highlightedText = originalText.replace(
  65. regex,
  66. `<span style="color:${warningColor};font-weight:bold;text-decoration:underline;">$&</span>`
  67. );
  68.  
  69. if (highlightedText !== originalText) {
  70. textContainer.innerHTML = highlightedText;
  71. const editPostButton = textContainer.parentNode.querySelector('.fa-edit');
  72. if (editPostButton) {
  73. editPostButton.style.color = warningColor;
  74. }
  75. }
  76. }
  77.  
  78. async function loadCurseWords() {
  79. // Try to get cached data first
  80. try {
  81. const cachedData = GM_getValue(CACHE_KEY);
  82. const cachedTime = GM_getValue(CACHE_TIMESTAMP);
  83. const cachedVersion = GM_getValue('curseWordsVersion');
  84. // If we have valid cached data and it's not expired
  85. if (cachedData && cachedTime && cachedVersion === CACHE_VERSION) {
  86. const age = Date.now() - parseInt(cachedTime, 10);
  87. if (age < CACHE_DURATION) {
  88. return JSON.parse(cachedData).words;
  89. }
  90. }
  91. // Fetch fresh data
  92. const response = await new Promise((resolve, reject) => {
  93. GM_xmlhttpRequest({
  94. method: 'GET',
  95. url: WORDS_JSON_URL,
  96. onload: resolve,
  97. onerror: reject,
  98. timeout: 5000 // 5 second timeout
  99. });
  100. });
  101. if (response.status >= 200 && response.status < 300) {
  102. const data = JSON.parse(response.responseText);
  103. // Cache the result
  104. GM_setValue(CACHE_KEY, JSON.stringify(data));
  105. GM_setValue(CACHE_TIMESTAMP, Date.now().toString());
  106. GM_setValue('curseWordsVersion', CACHE_VERSION);
  107. return data.words || DEFAULT_CURSE_WORDS;
  108. }
  109. } catch (error) {
  110. console.error('Failed to load curse words:', error);
  111. }
  112. // Fallback to default words
  113. return DEFAULT_CURSE_WORDS;
  114. }
  115.  
  116. function detectCurses() {
  117. const messages = document.querySelectorAll('.forum-post-content');
  118.  
  119. messages.forEach(message => {
  120. for (let i = 0; i < curseWords.length; i++) {
  121. searchAndHighlightWord(curseWords[i], message);
  122. }
  123. });
  124. }
  125.  
  126. function addCSSVariables() {
  127. const root = document.querySelector(':root');
  128. root.style.setProperty('--curseColor', '#ff4800');
  129. }
  130.  
  131. // Initialize the script
  132. async function init() {
  133. try {
  134. // Load curse words
  135. curseWords = await loadCurseWords();
  136. // Set up mutation observer
  137. const observer = new MutationObserver(mutations => {
  138. mutations.forEach(mutation => {
  139. if (mutation.type === 'childList') {
  140. mutation.addedNodes.forEach(node => {
  141. if (node.nodeType === 1 && node.classList.contains('forum-post-content')) {
  142. detectCurses();
  143. }
  144. });
  145. }
  146. });
  147. });
  148.  
  149. // Find posts container and start observing
  150. const postsContainer = document.querySelector('.forum_content');
  151. if (postsContainer) {
  152. addCSSVariables();
  153. observer.observe(postsContainer, { childList: true, subtree: true });
  154. // Initial check
  155. detectCurses();
  156. // Add a small indicator
  157. addStatusIndicator();
  158. } else {
  159. console.error('Posts container not found.');
  160. }
  161. } catch (error) {
  162. console.error('Error initializing script:', error);
  163. }
  164. }
  165.  
  166. // Add a small status indicator
  167. function addStatusIndicator() {
  168. const indicator = document.createElement('div');
  169. indicator.style.position = 'fixed';
  170. indicator.style.bottom = '10px';
  171. indicator.style.right = '10px';
  172. indicator.style.padding = '5px 10px';
  173. indicator.style.background = '#333';
  174. indicator.style.color = '#fff';
  175. indicator.style.borderRadius = '4px';
  176. indicator.style.fontSize = '12px';
  177. indicator.style.zIndex = '9999';
  178. indicator.textContent = `MZ Curse Detector v${GM_info.script.version} (${curseWords.length} palabras)`;
  179. document.body.appendChild(indicator);
  180. }
  181.  
  182. // Start the script
  183. init();