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');
			}
		});
	});
}

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址