🏠 Home 

Twitch [Chatters Count]

Shows the amount of people in the chat

// ==UserScript==
// @name         Twitch [Chatters Count]
// @namespace    https://github.com/pabli24
// @version      1.0.0
// @description  Shows the amount of people in the chat
// @author       Pabli
// @license      MIT
// @match        https://www.twitch.tv/*
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHBhdGggZmlsbD0iI2ZmODI4MCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTU2LjYgMTk2LjNhOTkuNCA5OS40IDAgMSAxIDEyMy4xIDk2LjUgMzkuNyAzOS43IDAgMCAwIDM1LjkgMjIuN2gxOS45YTU5LjYgNTkuNiAwIDAgMSA1OS42IDU5LjZ2MzkuOGgtMzkuOHYtMzkuOGMwLTExLTguOS0xOS45LTE5LjktMTkuOWgtMTkuOWE3OS40IDc5LjQgMCAwIDEtNTkuNi0yNi45IDc5LjUgNzkuNSAwIDAgMS01OS42IDI2LjloLTE5LjljLTExIDAtMTkuOSA4LjktMTkuOSAxOS45djM5LjhoLTM5Ljh2LTM5LjhhNTkuNiA1OS42IDAgMCAxIDU5LjYtNTkuNmgxOS45YzE1LjQgMCAyOS40LTguOCAzNS45LTIyLjdhOTkuNCA5OS40IDAgMCAxLTc1LjctOTYuNlpNMjU2IDI1NmE1OS42IDU5LjYgMCAxIDEgMC0xMTkuMiA1OS42IDU5LjYgMCAwIDEgMCAxMTkuMloiLz48cGF0aCBmaWxsPSIjZmY4MjgwIiBkPSJNMCA1MTJWMGgxMTYuOHYzNi4xSDQzLjN2NDQxLjNoNzMuNlY1MTJIMFpNNTEyIDB2NTEySDM5NS4ydi0zNi4xaDczLjZWMzQuNmgtNzMuNlYwSDUxMloiLz48L3N2Zz4=
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @grant        GM_notification
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// ==/UserScript==
(async () => {
'use strict';
let settings = {
previewCards: {
value: await GM_getValue('previewCards', true),
label: 'Show for preview cards in main/directory page',
},
underPlayer: {
value: await GM_getValue('underPlayer', true),
label: 'Show under the player next to the viewer count',
},
theatreMode: {
value: await GM_getValue('theatreMode', true),
label: 'Show in the theatre mode when mousing over the player',
},
topChat: {
value: await GM_getValue('topChat', false),
label: 'Show at the top of the chat',
},
numberFormat: {
value: await GM_getValue('numberFormat', true),
label: 'Number format based on your language (en-US if disabled)',
},
};
let menuCommands = {};
async function updateMenu() {
Object.keys(menuCommands).forEach(key => GM_unregisterMenuCommand(menuCommands[key]));
Object.entries(settings).forEach(([key, config]) => {
menuCommands[key] = GM_registerMenuCommand(
`${config.value ? '☑' : '☐'} ${config.label}`,
async () => {
settings[key].value = !settings[key].value;
await GM_setValue(key, settings[key].value);
GM_notification(`${config.label} is now ${config.value ? 'enabled' : 'disabled'}`, GM_info.script.name, GM_info.script.icon);
updateMenu();
}
);
});
}
updateMenu();
let updater = null;
let currentChannel = '';
setInterval(() => {
const path = window.location.pathname;
if (settings.previewCards.value && (path === '/' || path.startsWith('/directory'))) {
checkForNewCards();
return;
}
let channelName = path.split('/')[1];
if (!channelName || ['settings', 'subscriptions', 'inventory', 'wallet', 'privacy', 'annual-recap'].includes(channelName)) return;
if (channelName === 'popout') {
channelName = path.split('/')[2];
}
if (currentChannel !== channelName) {
currentChannel = channelName;
if (updater) {
clearInterval(updater);
updater = null;
}
}
if (settings.underPlayer.value) {
chattersCount();
} else if (ct) {
ct.remove();
ct = null;
}
if (settings.theatreMode.value) {
tmChattersCount();
} else if (tmCt) {
tmCt.remove();
tmCt = null;
}
if (settings.topChat.value) {
chatChattersCount();
} else if (chatCt) {
chatCt.remove();
chatCt = null;
}
if (!updater) {
updateCount(channelName);
updater = setInterval(() => updateCount(channelName), 60000);
}
}, 1500);
let ct = null;
let tmCt = null;
let chatCt = null;
let chatters = '⏳';
async function updateCount(channelName) {
chatters = await getChatters(channelName);
[ct, tmCt, chatCt].forEach(el => {
if (el) el.textContent = `[${chatters}]`;
});
}
function createChattersCounter(id) {
const counter = document.createElement('span');
counter.id = id;
counter.title = 'Chatters Count';
counter.textContent = `[${chatters}]`;
counter.style.cssText = `
color: var(--color-text-live, #ff8280);
font-size: var(--font-size-base, 1.3rem);
font-weight: var(--font-weight-semibold, 600);
font-feature-settings: "tnum";
line-height: var(--line-height-body, 1.5);
margin-left: 0.5rem;
align-content: center;
`;
return counter;
}
function chattersCount() {
ct = document.getElementById('chatters-count');
if (ct != null) return;
const viewersCount = document.querySelector('p[data-a-target="animated-channel-viewers-count"]')?.parentElement;
if (!viewersCount) return;
ct = createChattersCounter('chatters-count');
viewersCount.appendChild(ct);
}
function tmChattersCount() {
tmCt = document.getElementById('tm-chatters-count');
if (tmCt != null) return;
const tmInfoCard = document.querySelector('p[data-test-selector="stream-info-card-component__description"]');
if (!tmInfoCard) return;
tmCt = createChattersCounter('tm-chatters-count');
tmInfoCard.appendChild(tmCt);
}
function chatChattersCount() {
chatCt = document.getElementById('chat-chatters-count');
if (chatCt != null) return;
const chatHeader = document.querySelector('button[data-test-selector="chat-viewer-list"]')?.parentElement;
if (!chatHeader) return;
chatCt = createChattersCounter('chat-chatters-count');
chatHeader.prepend(chatCt);
}
function checkForNewCards() {
const previewCards = document.querySelectorAll('a[data-a-target="preview-card-image-link"]');
previewCards.forEach(addChattersToCard);
}
async function addChattersToCard(card) {
const cardStat = card.querySelector('div.tw-media-card-stat');
if (!cardStat || cardStat.querySelector('.directory-chatters-count') || card.dataset.chattersLoading === 'true') return;
card.dataset.chattersLoading = 'true';
const channelName = card.getAttribute('href').slice(1);
const counter = document.createElement('span');
counter.className = 'directory-chatters-count';
counter.style.paddingLeft = '0.4rem';
const count = await getChatters(channelName);
counter.textContent = `[${count}]`;
cardStat.appendChild(counter);
delete card.dataset.chattersLoading;
}
let lang = settings.numberFormat.value ? '' : 'en-US';
async function getChatters(channel) {
if (!lang) lang = document.documentElement.getAttribute('lang') || 'en-US';
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://gql.twitch.tv/gql',
headers: {
'Client-Id': 'kimne78kx3ncx6brgo4mv6wki5h1ko',
'Content-Type': 'application/json'
},
data: JSON.stringify({
query: `query { channel(name: "${channel}") { chatters { count } } }`
}),
onload: response => {
const data = JSON.parse(response.responseText);
const count = data?.data?.channel?.chatters?.count;
resolve(count != null && !isNaN(count) ? new Intl.NumberFormat(lang).format(count) : 'N/A');
}
});
});
}
})();