// ==UserScript==
// @name Viewer list bot remover
// @namespace https://greasyfork.org/scripts?set=586193
// @version 1.2.0
// @description Places bots in their own category in the viewer list.
// @author Sonyo
// @match http*://www.twitch.tv/*
// @grant none
// @license MIT
// @icon https://cdn-icons-png.flaticon.com/512/9092/9092067.png
// ==/UserScript==
/*
* OPTIONS
* Modify the following variable for the behavior concerning moderator bots:
*/
const modBotsBehavior = 0;
/*
* 0: Keep them in the Moderators panel
* 1: Place them with the other bots
* 2: Place them in their own panel
*/
let viewersPanelName = "";
let moderatorsPanelName = "";
const botsPanelTitle = "Bots";
const botsPanelDescription = "Description for bots... Well they're bots :)";
const botImageSource = "https://cdn-icons-png.flaticon.com/512/9092/9092067.png";
const modBotsPanelTitle = "Moderator bots";
const modBotsPanelDescription = "Bots used for the moderation of this channel.";
const modBotImageSource = "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/2";
// Must keep in alphabetical order !!
const botList = [
"01ella",
"0ax2",
"0niva",
"24_7_chatting_on_discord",
"a_ok",
"abjuui",
"actually__hannah",
"adwin666",
"aliceydra",
"aliengathering",
"anotherttvviewer",
"aten",
"audycia",
"avasemaphore",
"blgdamjudge",
"buttsbot",
"captainskrew",
"commanderroot",
"creatisbot",
"digitalinstinct",
"discordstreamercommunity",
"drapsnatt",
"ezobay",
"freakybabetv",
"iisabei",
"jinal",
"kattah",
"lucentcrown1234567891011",
"lurxx",
"lylituf",
"maddynsun",
"moobot",
"network_streamer_discord",
"nightbot",
"ninatela",
"paradise_for_streamers",
"pdp_bot",
"qnfgogtktls",
"rogueg1rl",
"roxesy",
"saralna",
"soundalerts",
"srekrapstob",
"streamelements",
"thisisunreallol",
"waptart",
"wizebot",
"yamickle",
];
var viewerListShown = false;
var botsPanel = null;
function delay(milliseconds) {
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
}
async function detectLanguage() {
var div = document.querySelector('[class="ScInputBase-sc-vu7u7d-0 ScInput-sc-19xfhag-0 gXVFsI jhrDHh InjectLayout-sc-1i43xsx-0 kvZgmT tw-input tw-input--large"]');
let count = 0;
while (div === null) {
await delay(1000);
div = document.querySelector('[class="ScInputBase-sc-vu7u7d-0 ScInput-sc-19xfhag-0 gXVFsI jhrDHh InjectLayout-sc-1i43xsx-0 kvZgmT tw-input tw-input--large"]');
count++;
if (count > 15) {
console.log("[Viewer list bot remover]: Search bar not found, script not working");
return;
}
}
let searchText = div.placeholder;
switch (searchText) {
case "Search":
viewersPanelName = "Viewers";
moderatorsPanelName = "Moderators";
break;
case "Søg":
viewersPanelName = "Seere";
moderatorsPanelName = "Moderatorer";
break;
case "Suchen":
viewersPanelName = "Zuschauer";
moderatorsPanelName = "Moderatoren";
break;
case "Buscar":
viewersPanelName = "Espectadores";
moderatorsPanelName = "Moderadores";
break;
case "Rechercher":
viewersPanelName = "Spectateurs";
moderatorsPanelName = "Modérateurs";
break;
case "Cerca":
viewersPanelName = "Spettatori";
moderatorsPanelName = "Moderatori";
break;
case "Keresés":
viewersPanelName = "Nézők";
moderatorsPanelName = "Moderátorok";
break;
case "Zoeken":
viewersPanelName = "Kijkers";
moderatorsPanelName = "Moderators";
break;
case "Søk":
viewersPanelName = "Seere";
moderatorsPanelName = "Moderatorer";
break;
case "Wyszukaj":
viewersPanelName = "widzowie";
moderatorsPanelName = "Moderatorzy";
break;
case "Pesquisa":
viewersPanelName = "Espetadores";
moderatorsPanelName = "Moderadores";
break;
case "Căutare":
viewersPanelName = "Vizualizatori";
moderatorsPanelName = "Moderatori";
break;
case "Hľadať":
viewersPanelName = "Diváci";
moderatorsPanelName = "Moderátori";
break;
case "Etsi":
viewersPanelName = "Katsojat";
moderatorsPanelName = "Moderaattorit";
break;
case "Sök":
viewersPanelName = "Tittare";
moderatorsPanelName = "Moderatorer";
break;
case "Tìm kiếm":
viewersPanelName = "Người xem";
moderatorsPanelName = "Người điều hành";
break;
case "Ara":
viewersPanelName = "İzleyici";
moderatorsPanelName = "Moderatörler";
break;
case "Hledat":
viewersPanelName = "Diváci";
moderatorsPanelName = "Moderátoři";
break;
case "Αναζήτηση":
viewersPanelName = "Θεατές";
moderatorsPanelName = "Επόπτες";
break;
case "Търсене":
viewersPanelName = "Зрители";
moderatorsPanelName = "Модератори";
break;
case "Поиск":
viewersPanelName = "Зрители";
moderatorsPanelName = "Модераторы";
break;
case "ค้นหา":
viewersPanelName = "ผู้ชม";
moderatorsPanelName = "ผู้ดำเนินรายการ";
break;
case "搜索":
viewersPanelName = "观众";
moderatorsPanelName = "管理员";
break;
case "搜尋":
viewersPanelName = "觀眾";
moderatorsPanelName = "Mod";
break;
case "検索":
viewersPanelName = "視聴者数";
moderatorsPanelName = "モデレーター";
break;
case "검색":
viewersPanelName = "시청자 수";
moderatorsPanelName = "매니저";
break;
default:
alert("Unknown language !");
break;
}
//alert(`viewers:${viewersPanelName}, mods:${moderatorsPanelName}`);
}
void async function () {
'use strict';
await detectLanguage();
let prevUrl = undefined;
setInterval(async () => {
const currUrl = window.location.href;
if (currUrl != prevUrl) {
prevUrl = currUrl;
await setup();
}
}, 60);
}();
async function setup() {
var communityButton = document.querySelector('[data-test-selector="chat-viewer-list"]');
let count = 0;
while (communityButton === null) {
await delay(1000);
communityButton = document.querySelector('[data-test-selector="chat-viewer-list"]');
count++;
if (count > 15) {
console.log("[Viewer list bot remover]: Community button not found, script not working");
return;
}
}
viewerListShown = false;
communityButton.addEventListener("click", communityButtonClick);
}
async function getContainer() {
// Get the viewers container
var scrollable = null;
let count = 1;
while (scrollable === null) {
scrollable = document.querySelector('[class="scrollable-area scrollable-area--suppress-scroll-x"]');
count++;
if (count > 300) // 50ms * 300 = 15s
{
console.log("[Viewer list bot remover]: Loading took too long");
return;
}
await delay(50);
}
return scrollable.lastChild.firstChild.firstChild;
}
function binarySearch(name) {
let start = 0;
let end = botList.length - 1;
while (start <= end) {
let mid = Math.floor((start + end) / 2);
if (botList[mid] === name) return true;
if (botList[mid] < name) start = mid + 1;
else end = mid - 1;
}
return false;
}
function removeBots(panel, container) {
let viewers = panel.firstChild.lastChild;
let bots = [];
for (let i = 0; i < viewers.children.length; i++) {
let viewer = viewers.children[i];
let name = viewer.firstChild.firstChild.firstChild.firstChild.textContent;
let remove = binarySearch(name.toLowerCase());
if (remove) {
viewer.remove();
bots.push(viewer);
i--;
}
}
// If there is no more viewers, remove the panel
if (viewers.children.length === 0) {
panel.remove();
}
return bots;
}
function createBotsPanel(panel, container, options) {
let newPanel = panel.cloneNode(true); // Doesn't copy event listeners FeelsSadMan
let botImg = document.createElement("img");
botImg.setAttribute("class", "InjectLayout-sc-1i43xsx-0 lfGYGL tw-image");
botImg.setAttribute("alt", "Bot badge");
botImg.setAttribute("src", options.ImageSrc);
newPanel.firstChild.children[0].firstChild.children[0].remove();
newPanel.firstChild.children[0].firstChild.insertBefore(botImg, newPanel.firstChild.children[0].firstChild.firstChild);
newPanel.firstChild.children[0].firstChild.children[1].firstChild.innerHTML = options.Title;
newPanel.firstChild.children[1].innerHTML = options.Description;
let viewers = newPanel.firstChild.children[2];
while (viewers.firstChild) {
viewers.removeChild(viewers.firstChild);
}
container.appendChild(newPanel);
return newPanel;
}
function handleViewerPanel(panel, container) {
let bots = removeBots(panel, container);
if (bots.length === 0) {
return;
}
if (botsPanel === null) {
botsPanel = createBotsPanel(panel, container, {ImageSrc: botImageSource, Title: botsPanelTitle, Description: botsPanelDescription});
}
let viewers = botsPanel.firstChild.lastChild;
for (let bot of bots) {
viewers.appendChild(bot);
}
}
function handleModeratorPanel(panel, container) {
if (modBotsBehavior < 0 || modBotsBehavior > 2) {
alert("[Viewer list bot remover]: modBotsBehavior incorrectly set.");
return;
}
if (modBotsBehavior === 0) {
return;
}
let bots = removeBots(panel, container);
if (bots.length === 0) {
return;
}
let viewers;
if (modBotsBehavior === 1) {
botsPanel = createBotsPanel(panel, container, {ImageSrc: botImageSource, Title: botsPanelTitle, Description: botsPanelDescription});
viewers = botsPanel.firstChild.lastChild;
}
if (modBotsBehavior === 2) {
let modBotsPanel = createBotsPanel(panel, container, {ImageSrc: modBotImageSource, Title: modBotsPanelTitle, Description: modBotsPanelDescription})
viewers = modBotsPanel.firstChild.lastChild;
}
for (let bot of bots) {
viewers.insertBefore(bot, viewers.firstChild);
}
}
async function communityButtonClick() {
if (viewerListShown) {
viewerListShown = false;
return;
}
viewerListShown = true;
var container = await getContainer();
if (container.children.length === 1) {
// No one in chat
return;
}
let endScroll = container.lastChild.lastChild.lastChild.lastChild;
endScroll.remove();
botsPanel = null;
for (let i = 1; i < container.children.length; i++) {
let panel = container.children[i];
let panelName = panel.firstChild.firstChild.firstChild.children[1].firstChild.firstChild.textContent;
switch (panelName) {
case moderatorsPanelName:
handleModeratorPanel(panel, container);
break;
case viewersPanelName:
handleViewerPanel(panel, container);
break;
}
if (panel.parentElement === null) {
// Panel was empty, and got removed
i--;
}
}
container.lastChild.lastChild.lastChild.appendChild(endScroll);
}