Skip to content

Instantly share code, notes, and snippets.

@flipeador
Last active July 22, 2025 02:29
Show Gist options
  • Save flipeador/7cdead05b6f00b34b743dbde071db34d to your computer and use it in GitHub Desktop.
Save flipeador/7cdead05b6f00b34b743dbde071db34d to your computer and use it in GitHub Desktop.
Show Twitch channel chatter count next to the channel viewer count.
// ==UserScript==
// @name Twitch Chatters Count
// @author Flipeador
// @version 1.0.5
// @icon https://www.google.com/s2/favicons?sz=128&domain=twitch.tv
// @homepageURL https://gist.github.com/flipeador/7cdead05b6f00b34b743dbde071db34d
// @downloadURL https://gist.githubusercontent.com/flipeador/7cdead05b6f00b34b743dbde071db34d/raw/twitch-chatter-count.js
// @require https://gist.githubusercontent.com/flipeador/577200d2ece50b4e0906d1ff589f1935/raw/graphql-tag.js
// @match *://www.twitch.tv/*
// @run-at document-idle
// ==/UserScript==
let viewers, chatters;
class Twitch {
static async gql(query, variables) {
const client = Twitch.getApolloClient();
return client.query({ query, variables });
}
static getReactRoot() {
const $root = document.querySelector('#root');
const filter = k => k.startsWith('__reactContainer$');
return $root[Object.keys($root).find(filter)];
}
static findReactChildren(node, predicate) {
while (node) {
const result = predicate(node);
if (result) return { node, result };
node = node?.child || node?.sibling;
}
}
static getApolloClient() {
const root = Twitch.getReactRoot();
const filter = n => n.pendingProps?.value?.client;
return Twitch.findReactChildren(root, filter)?.result;
}
static getCurrentChannelName() {
return location.pathname.split('/')[1];
}
static getViewersElement() {
return document.querySelector('[data-a-target=animated-channel-viewers-count]');
}
static async getChattersCount(name) {
const query = gql`
query GetChannelChattersCount($name: String!) {
channel(name: $name) {
chatters {
count
}
}
}`;
const json = await Twitch.gql(query, { name });
return json?.data?.channel?.chatters?.count;
}
}
function checkElementTime($element, ms, ts=Date.now()) {
const timestamp = Number($element.dataset.timestamp);
if (timestamp && ts - timestamp <= ms) return;
return $element.dataset.timestamp = ts;
}
async function updateChattersCount($viewers) {
if (!checkElementTime($viewers, 60000)) return;
const channel = Twitch.getCurrentChannelName();
chatters = await Twitch.getChattersCount(channel);
console.log('Channel chatters count:', chatters);
}
function setTwitchTrackerClickHandler($viewers) {
if ($viewers.dataset.twitchtracker) return;
$viewers.dataset.twitchtracker = true;
$viewers.title = 'Twitch Tracker';
$viewers.style.cursor = 'pointer';
$viewers.addEventListener('click', () => {
const channel = Twitch.getCurrentChannelName();
open(`https://twitchtracker.com/${channel}`, '_blank');
});
}
async function main() {
const $viewers = Twitch.getViewersElement();
if ($viewers) {
setTwitchTrackerClickHandler($viewers);
const count = parseInt($viewers.textContent);
const haslb = $viewers.textContent.includes('[');
if (count && (viewers !== count || !haslb)) {
if (viewers !== count)
await updateChattersCount($viewers, viewers = count);
$viewers.replaceChildren($viewers.firstChild);
$viewers.append(new Text(` [${chatters}]`));
}
}
setTimeout(main, 2500);
}
main();
@flipeador
Copy link
Author

flipeador commented Jun 14, 2025

Twitch Chatter Count

20250614015031

Show Twitch channel chatter count next to the channel viewer count.

Installation

  1. Install the Tampermonkey browser extension.
  2. Import the script from the extension Dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment