Greasy Fork is available in English.

Twitch detect repeated words

repeated words play alert sound

// ==UserScript==
// @name               Twitch detect repeated words
// @namespace          https://greasyfork.org/users/821661
// @match              https://www.twitch.tv/*
// @grant              none
// @version            1.0
// @author             hdyzen
// @description        repeated words play alert sound
// @license            GPL-3.0
// ==/UserScript==
'use strict';

const opcional_words = []; // Example ['!enter', '!join', '!give', 'someword'] NOTE: Leave empty to detect any word
const min_letters = 2;
const min_repeated_words = 5;
const time = minutesToMs(10);
const audio = new Audio('https://cdn.pixabay.com/audio/2024/02/07/audio_9e3b3e9dcc.mp3');
const words = new Map();

function processWords(mutation) {
    const messageElement = mutation.addedNodes[0]?.querySelector('.message');
    const messageContent = messageElement?.textContent;

    if (messageContent) {
        let wordsClean = messageContent.replace(/[^\w\sÀ-ú](?!\w)/g, '');
        let wordsOut = wordsClean.split(' ');
        let countedWordsInArray = new Set();

        wordsOut.forEach(word => {
            if (word === '' || countedWordsInArray.has(word)) return;
            countedWordsInArray.add(word);
            const wordInMap = words.get(word);
            const countWords = wordInMap === undefined ? 0 : wordInMap;

            if (((opcional_words.length && opcional_words.includes(word)) || opcional_words.length === 0) && word.length > min_letters && countWords > min_repeated_words) {
                audio.play();
                messageElement.innerHTML = messageContent.replace(new RegExp(`\\b${word}\\b`, 'gm'), `<span style="border: 1px solid red;">${word}</span>`);
            } else {
                words.set(word, countWords + 1);
            }
        });
    }
}

function minutesToMs(m) {
    let seconds = m * 60;
    let ms = seconds * 1000;
    return ms;
}

const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        if (mutation.type === 'childList' && mutation.target.classList.contains('chat-scrollable-area__message-container')) processWords(mutation);
    });
});

observer.observe(document.body, { childList: true, subtree: true });

setInterval(e => {
    words.clear();
}, time);