FT.live Message reader

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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();
})();