Twitch channel Viewers count in window's title

Showing Viewers # in the window's title, refreshing when it changes, blinking green if +25 blinking red if -25

Versão de: 07/05/2025. Veja: a última versão.

// ==UserScript==
// @name         Twitch channel Viewers count in window's title
// @namespace    http://tampermonkey.net/
// @version      2025-05-07
// @description  Showing Viewers # in the window's title, refreshing when it changes, blinking green if +25 blinking red if -25
// @author       Guile93
// @match        https://www.twitch.tv/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitch.tv
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
window.addEventListener("load", () => {
  let originalTitle = document.title.replace(/\s+\[\d+\]\s*$/, '').replace(/\s+-\s+Twitch$/, '');
  let lastViewerCount = null;
  let flashing = false;

  // Vérifie régulièrement si le titre de base a changé (changement de chaîne)
  setInterval(() => {
    const cleanedTitle = document.title.replace(/\s+\[\d+\]\s*$/, '').replace(/\s+-\s+Twitch$/, '');
    if (cleanedTitle !== originalTitle && !flashing) {
      originalTitle = cleanedTitle;
      if (lastViewerCount !== null) {
        document.title = `${originalTitle} [${lastViewerCount}]`;
      }
    }
  }, 3000);

  function updateTitle(nb_viewer, flashColor = null) {
    if (flashColor) {
      if (flashing) return; // Empêche les clignotements qui se chevauchent
      flashing = true;
      let visible = true;
      const flashText = color => {
        switch (color) {
          case "green": return `🟢`;
          case "red": return `🔴`;
          default: return ``;
        }
      };

      const interval = setInterval(() => {
        visible = !visible;
        document.title = visible
          ? `${originalTitle} [${nb_viewer}] ${flashText(flashColor)}`
          : `${originalTitle}`;
      }, 500);

      setTimeout(() => {
        clearInterval(interval);
        document.title = `${originalTitle} [${nb_viewer}]`;
        flashing = false;
      }, 10000);
    } else {
      if (!flashing) {
        document.title = `${originalTitle} [${nb_viewer}]`;
      }
    }
  }

  function waitForElement(selector, callback) {
    const el = document.querySelector(selector);
    if (el) {
      callback(el);
    } else {
      const observer = new MutationObserver(() => {
        const el = document.querySelector(selector);
        if (el) {
          observer.disconnect();
          callback(el);
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });
    }
  }

  waitForElement(".tw-stat__value", (viewerElement) => {
    const handleChange = () => {
      const text = viewerElement.textContent.trim().replace(/[^\d]/g, '');
      const current = parseInt(text, 10);
      if (!isNaN(current)) {
        let flashColor = null;
        if (lastViewerCount !== null) {
          const diff = current - lastViewerCount;
          if (diff >= 25) flashColor = "green";
          else if (diff <= -25) flashColor = "red";
        }
        updateTitle(current, flashColor);
        lastViewerCount = current;
      }
    };

    handleChange(); // Initialisation
    const observer = new MutationObserver(handleChange);
    observer.observe(viewerElement, { childList: true, characterData: true, subtree: true });
  });
});
})();