FT.live Message reader

3/19/2025, 2:23:22 PM

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        FT.live Message reader
// @namespace   Violentmonkey Scripts
// @match       https://www.fishtank.live/*
// @grant       none
// @version     1.0
// @license     AGPL-3.0
// @author      Me :)
// @description 3/19/2025, 2:23:22 PM
// ==/UserScript==

const USERNAME = 'FT_USERNAME';
const SPEECH_PITCH = 1.0;
const SPEECH_RATE = 1.0;
const MAX_TTS_QUEUED = 3; // If there's a bunch of messages in a row, the get queued up, if the queue is full the most recent message will be replaced
const MS_BETWEEN_TTS = 3000; // Time in between TTS in milliseconds
const PREFERRED_VOICES = ['Asilia', 'Aria', 'Google'];
const LANGS = ['en-US', 'en-KE']; // Accepted voice languages

(function() {
    window.playingTTS = false;
    window.ttsQueue = [];
    'use strict';
    const voices = speechSynthesis.getVoices();

    function chooseVoice() {
      const voices = speechSynthesis.getVoices();
      if (voices.length === 0) {
        console.log('No TTS voices available.');
        return null;
      }
      let fallback = null;
      for (let j = 0; j < PREFERRED_VOICES.length; j++) {
        for (let i = 0; i < voices.length; i++) {
          const v = voices[i];
          if (LANGS.indexOf(v.lang) === -1) continue;
          if (fallback === null) fallback = v;

          if (v.name.includes(PREFERRED_VOICES[j])) {
            return v;
          }
        }
      }
      return fallback || voices[0];
    }

    function speakTextAdvanced(text, forceTTS=false) {
      if (window.playingTTS && !forceTTS) {
        if (window.ttsQueue.length >= MAX_TTS_QUEUED) {
          window.ttsQueue[MAX_TTS_QUEUED-1] = text; // replace last message in queue
        } else {
          window.ttsQueue.push(text);
        }
        return;
      }
      const utterance = new SpeechSynthesisUtterance(text);
      utterance.rate = SPEECH_RATE; // Speed (0.1 - 10, default: 1)
      utterance.pitch = SPEECH_PITCH; // Pitch (0 - 2, default: 1)

      // Get available voices and select one
      utterance.voice = chooseVoice();
      if (utterance.voice === null) return;

      utterance.onend = () => {
        if (window.ttsQueue.length === 0) {
          window.playingTTS = false;
        } else {
          setTimeout(() => speakTextAdvanced(window.ttsQueue.pop(), true), MS_BETWEEN_TTS);
        }
      };

      speechSynthesis.speak(utterance);
      window.playingTTS = true;
    }

    function processChatMessage(node) {
        const msgSpans = node.getElementsByClassName('chat-message-default_message__milmT');
        if (msgSpans.length == 0) return;
        const msg = msgSpans[0].innerText.toLocaleLowerCase();
        if (msg.indexOf(USERNAME.toLocaleLowerCase()) !== -1) {
          let cleanMsg = msg.replaceAll('@' + USERNAME, '');
          cleanMsg = cleanMsg.replaceAll(USERNAME, '');
          speakTextAdvanced(cleanMsg);
        }
    }

    // Function to handle new messages
    function handleNewMessage(mutationsList) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1 && node.tagName === 'DIV') {
                        processChatMessage(node);
                    }
                });
            }
        }
    }

    // Wait for the chat container to be available
    function waitForChatContainer() {
        const chatContainer = document.getElementById("chat-messages");
        if (chatContainer) {
            const observer = new MutationObserver(handleNewMessage);
            observer.observe(chatContainer, { childList: true });
            console.log("Observer started on #chat-messages");
        } else {
            setTimeout(waitForChatContainer, 500);
        }
    }

    waitForChatContainer();
})();