// ==UserScript==
// @name Kick.com Combined Chatter and Message Counter
// @version 4.01
// @description Kick.com Combined Chatter and Message Counter - TRUTH EDITION
// @author Graph1ks
// @match https://kick.com/*
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @namespace https://greasyfork.org/de/users/1245263
// ==/UserScript==
(function () {
'use strict';
let consoleLogsActive = true;
let viewerCountValue = '<span class="viewer_countUI" style="color: orange;">calc</span>';
let currentHttpRequest;
let initialPath = window.location.pathname;
let initialUsername = getStreamerUsernameFromURL();
let startTime = window.startTime || new Date();
const userData = {
uniqueMessages: new Set(),
chattersPerMinute: new Map(),
messagesPerMinute: new Map(),
totalChatters: [],
totalMessages: [],
};
const logData = [];
const uiContainer = document.createElement('div');
uiContainer.style.position = 'fixed';
uiContainer.style.top = '0px';
uiContainer.style.right = '60px';
uiContainer.style.padding = '0px';
uiContainer.style.background = 'transparent';
uiContainer.style.color = '#fff';
uiContainer.style.cursor = 'pointer';
uiContainer.style.zIndex = '9999';
uiContainer.style.textAlign = 'left';
uiContainer.style.width = '300px';
document.body.appendChild(uiContainer);
updateUIPanel();
function addLogToData(log) {
logData.push(log);
}
function toggleConsoleLogs() {
consoleLogsActive = !consoleLogsActive;
consoleLogsActive ? enableConsoleLogs() : disableConsoleLogs();
}
function enableConsoleLogs() {
console.log = console._originalLog;
console.warn = console._originalWarn;
console.error = console._originalError;
}
function disableConsoleLogs() {
console._originalLog = console.log;
console._originalWarn = console.warn;
console._originalError = console.error;
console.log = function (...args) {
const logMessage = args.join(' ');
addLogToData(logMessage);
};
console.warn = function (...args) {
const logMessage = args.join(' ');
addLogToData(`Warning: ${logMessage}`);
};
console.error = function (...args) {
const logMessage = args.join(' ');
addLogToData(`Error: ${logMessage}`);
};
}
function getStreamerUsernameFromURL() {
const urlParts = window.location.href.split('/');
const username = urlParts[urlParts.length - 1];
console.log(`Streamer username extracted from URL: ${username}`);
return username;
}
let viewerCount = 0;
const fetchLivestreamViewerCount = () => {
const apiUrl = `https://kick.com/api/v2/channels/${getStreamerUsernameFromURL()}`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
responseType: 'json',
onload: function (response) {
if (response.status === 200) {
const responseData = JSON.parse(response.responseText);
const newViewerCount = responseData.livestream.viewer_count;
if (newViewerCount !== undefined) {
viewerCount = newViewerCount;
updateViewerCount();
console.log(`Viewer count: ${viewerCount}`);
viewerCountValue = viewerCount !== undefined
? viewerCount
: '<span class="viewer_countUI" style="color: orange;">calc</span>';
updateUIPanel();
} else {
console.error('Failed to extract viewer count from API response.');
}
} else {
console.error('Error fetching data from API. Status:', response.status);
}
},
onerror: function (error) {
console.error('Error making API request:', error);
},
});
};
function updateViewerCount() {
const viewerCountElement = document.querySelector('.viewer_countUI');
if (viewerCountElement) {
viewerCountElement.innerHTML = viewerCount !== undefined
? viewerCount
: '<span class="viewer_countUI" style="color: orange;">calc</span>';
updateUIPanel();
} else {
console.error('Viewer count element not found. Unable to update viewer count.');
}
}
function parseChatEntry(entryElement) {
const currentTime = new Date().toLocaleTimeString();
const entryId = entryElement.getAttribute('data-chat-entry');
const usernameElement = entryElement.querySelector('.chat-entry-username');
const contentElement = entryElement.querySelector('.chat-entry-content');
if (entryId && usernameElement && contentElement) {
const username = usernameElement.dataset.chatEntryUser;
const content = contentElement.textContent.trim();
const message = {
timestamp: currentTime,
entryId,
username,
content,
};
return message;
}
return null;
}
function checkForNewChatEntries() {
if (!consoleLogsActive) return;
const chatEntries = document.querySelectorAll('[data-chat-entry]');
const newChatters = new Set();
const newMessages = new Set();
chatEntries.forEach((entry) => {
const chatEntry = parseChatEntry(entry);
if (chatEntry && !userData.uniqueMessages.has(chatEntry.entryId)) {
userData.uniqueMessages.add(chatEntry.entryId);
newChatters.add(chatEntry.username);
newMessages.add(chatEntry.entryId);
}
});
console.log(`Number of unique messages stored: ${userData.uniqueMessages.size}`);
const currentTime = new Date();
const currentMinute = currentTime.getMinutes();
if (!userData.chattersPerMinute.has(currentMinute)) {
userData.chattersPerMinute.set(currentMinute, new Set());
}
const currentMinuteChatters = userData.chattersPerMinute.get(currentMinute);
newChatters.forEach((username) => currentMinuteChatters.add(username));
if (!userData.messagesPerMinute.has(currentMinute)) {
userData.messagesPerMinute.set(currentMinute, new Set());
}
const currentMinuteMessages = userData.messagesPerMinute.get(currentMinute);
newMessages.forEach((messageId) => currentMinuteMessages.add(messageId));
console.log(`Chatters per minute: ${currentMinuteChatters.size}`);
updateUIPanel();
}
setInterval(checkForNewChatEntries, 1000);
function logChattersAndMessagesPerMinute() {
if (!consoleLogsActive) return;
const currentMinute = new Date().getMinutes();
if (userData.chattersPerMinute.has(currentMinute)) {
const currentMinuteChatters = userData.chattersPerMinute.get(currentMinute);
userData.totalChatters.push(currentMinuteChatters.size);
}
if (userData.messagesPerMinute.has(currentMinute)) {
const currentMinuteMessages = userData.messagesPerMinute.get(currentMinute);
console.log(`Messages per minute (current minute): ${currentMinuteMessages.size}`);
userData.totalMessages.push(currentMinuteMessages.size);
}
const averageChatters = calculateAverage(userData.totalChatters);
console.log(`Average Chatters per minute: ${averageChatters !== undefined ? averageChatters : '<span style="color: orange;">calc</span>'}`);
const averageMessages = calculateAverage(userData.totalMessages);
console.log(`Average Messages per minute: ${averageMessages !== undefined ? averageMessages : '<span style="color: orange;">calc</span>'}`);
console.log(`Total Chatters: ${userData.totalChatters.reduce((sum, chatters) => sum + chatters, 0)}`);
console.log(`Average Chatters: ${averageChatters !== undefined ? averageChatters : '<span style="color: orange;">calc</span>'}`);
console.log(`Total Messages: ${userData.totalMessages.reduce((sum, messages) => sum + messages, 0)}`);
console.log(`Average Messages: ${averageMessages !== undefined ? averageMessages : '<span style="color: orange;">calc</span>'}`);
requestAnimationFrame(updateUIPanel);
}
function shouldDisplayCalc(type) {
const elapsedTime = (new Date() - startTime) / 1000; // Elapsed time in seconds
// Display "calc" in orange for the first 60 seconds
if (elapsedTime < 60) {
return true;
}
// After 60 seconds, only display "calc" for the specified type
switch (type) {
case 'chatters':
return userData.totalChatters.length === 0;
// Add more cases for other types if needed
default:
return false;
}
}
function updateUIPanel() {
const chatEntries = document.querySelectorAll('[data-chat-entry]');
// Check if there are any chat entries on the page
const hasChatEntries = chatEntries.length > 0;
// If there are no chat entries, hide the UI PANEL
if (!hasChatEntries) {
uiContainer.style.display = 'none';
return;
}
uiContainer.style.display = ''; // Display the UI PANEL
uiContainer.innerHTML = '';
const tableStyle = `
font-size: 10px;
color: white;
border-collapse: collapse;
margin-bottom: 5px;
width: 300px;
`;
const cellStyle = `
border: none;
padding: 2px;
text-align: center;
width: 100px;
`;
const numericalValueStyle = `color: #53FC18;`;
const tableElement = document.createElement('table');
tableElement.style = tableStyle;
const row1 = tableElement.insertRow();
const row2 = tableElement.insertRow();
const row3 = tableElement.insertRow();
const displayCalc = shouldDisplayCalc('chatters');
const addInfo = (label, value, row) => {
const labelCell = row.insertCell();
const valueCell = row.insertCell();
labelCell.style = cellStyle;
labelCell.innerHTML = `<strong>${label}</strong>`;
valueCell.style = `${cellStyle} ${numericalValueStyle}`;
valueCell.innerHTML = displayCalc ? '<span style="color: orange;">calc</span>' : value;
};
addInfo('Chatters:', userData.totalChatters.reduce((sum, chatters) => sum + chatters, 0), row1);
addInfo('per min:', calculateAverage(userData.totalChatters, true), row1);
addInfo('Messages:', userData.uniqueMessages.size, row2);
addInfo('per min:', calculateAverage(userData.totalMessages, true), row2);
const labelCell = row3.insertCell();
const valueCell = row3.insertCell();
const labelCell2 = row3.insertCell();
const elapsedTimeCell = row3.insertCell();
labelCell.style = cellStyle;
labelCell.innerHTML = '<strong> Viewers:</strong>';
valueCell.style = `${cellStyle} ${numericalValueStyle}`;
valueCell.innerHTML = viewerCountValue;
labelCell2.style = cellStyle;
labelCell2.innerHTML = '<strong> Timer:</strong>';
elapsedTimeCell.style = `${cellStyle} ${numericalValueStyle}`;
const elapsedTimeInSeconds = Math.floor((new Date() - startTime) / 1000);
const hours = Math.floor(elapsedTimeInSeconds / 3600);
const minutes = Math.floor((elapsedTimeInSeconds % 3600) / 60);
const seconds = elapsedTimeInSeconds % 60;
elapsedTimeCell.innerHTML = `${formatTime(hours)}h ${formatTime(minutes)}m ${formatTime(seconds)}s`;
uiContainer.appendChild(tableElement);
logData.forEach((log) => {
if (!log.includes('Console Logs:')) {
const logElement = document.createElement('div');
logElement.style = tableStyle;
logElement.textContent = log;
uiContainer.appendChild(logElement);
}
});
// Add an event listener to detect left mouse clicks on the uiContainer
uiContainer.addEventListener('click', function(event) {
if (event.button === 0) { // Check if it's a left mouse click
// Copy the content of the uiContainer to the clipboard
copyUIContentToClipboard();
}
});
}
// Helper function to format time components with leading zeros
function formatTime(time) {
return time < 10 ? `0${time}` : `${time}`;
}
// Add this function to copy the content of the uiContainer to the clipboard
function copyUIContentToClipboard() {
const streamerUsername = getStreamerUsernameFromURL();
const capitalizedStreamerUsername = streamerUsername.charAt(0).toUpperCase() + streamerUsername.slice(1);
const uiContent = `Streamer: ${capitalizedStreamerUsername}\n${uiContainer.innerText}`;
GM_setClipboard(uiContent, 'text');
// Get the current mouse position
const mouseX = window.event.clientX;
const mouseY = window.event.clientY;
// Show confirmation window at the mouse pointer location
const confirmationWindow = document.createElement('div');
confirmationWindow.style.position = 'fixed';
confirmationWindow.style.top = `${mouseY - 20}px`; // Slightly above the mouse pointer
confirmationWindow.style.left = `${mouseX}px`;
confirmationWindow.style.background = '#53FC18';
confirmationWindow.style.color = 'black';
confirmationWindow.style.padding = '5px';
confirmationWindow.style.borderRadius = '5px'; // Border radius
confirmationWindow.style.fontSize = '9px'; // Font size
confirmationWindow.style.textTransform = 'uppercase'; // Text to uppercase
confirmationWindow.style.fontWeight = 'bold'; // Bold text
confirmationWindow.style.zIndex = '10000';
confirmationWindow.textContent = 'Copied to clipboard';
document.body.appendChild(confirmationWindow);
// Remove confirmation window after a brief delay
setTimeout(() => {
document.body.removeChild(confirmationWindow);
}, 1000); // Stay visible for 1 second
}
function calculateAverage(array, displayCalc) {
if (array.length === 0) {
return displayCalc ? '<span style="color: orange;">calc</span>' : 0;
}
const sum = array.reduce((acc, value) => acc + value, 0);
const average = sum / array.length;
return displayCalc ? Math.round(average) : average;
}
function getChattersPerMinute() {
const currentMinute = new Date().getMinutes();
if (userData.chattersPerMinute.has(currentMinute)) {
const currentMinuteChatters = userData.chattersPerMinute.get(currentMinute);
return currentMinuteChatters.size;
}
return 0;
}
function addUIControls() {
const controlsContainer = document.createElement('div');
controlsContainer.style.marginTop = '5px';
controlsContainer.style.textAlign = 'center';
const toggleLogsButton = document.createElement('button');
toggleLogsButton.textContent = 'Toggle Console Logs';
toggleLogsButton.style.marginRight = '5px';
toggleLogsButton.addEventListener('click', toggleConsoleLogs);
const copyLogsButton = document.createElement('button');
copyLogsButton.textContent = 'Copy Logs to Clipboard';
copyLogsButton.addEventListener('click', copyLogsToClipboard);
controlsContainer.appendChild(toggleLogsButton);
controlsContainer.appendChild(copyLogsButton);
uiContainer.appendChild(controlsContainer);
}
function copyLogsToClipboard() {
const logsText = logData.join('\n');
GM_setClipboard(logsText, 'text');
console.log('Console logs copied to clipboard.');
}
function checkChanges() {
const currentPath = window.location.pathname;
const currentUsername = getStreamerUsernameFromURL();
if (currentPath !== initialPath || currentUsername !== initialUsername) {
initialPath = currentPath;
initialUsername = currentUsername;
resetScript();
fetchLivestreamViewerCount();
}
}
function resetScript() {
userData.uniqueMessages.clear();
userData.chattersPerMinute.clear();
userData.messagesPerMinute.clear();
userData.totalChatters = [];
userData.totalMessages = [];
logData.length = 0;
if (currentHttpRequest) {
currentHttpRequest.abort();
}
viewerCount = 0;
viewerCountValue = '<span class="viewer_countUI" style="color: orange;">calc</span>';
updateUIPanel();
}
setTimeout(() => {
fetchLivestreamViewerCount();
}, 500);
const viewerCountInterval = setInterval(() => {
fetchLivestreamViewerCount();
}, 30000);
const changesCheckInterval = setInterval(() => {
checkChanges();
}, 1000);
addUIControls();
const apiUrl = `https://kick.com/api/v2/channels/${getStreamerUsernameFromURL()}`;
fetchLivestreamViewerCount(apiUrl);
setInterval(() => {
fetchLivestreamViewerCount(apiUrl);
}, 30000);
setInterval(logChattersAndMessagesPerMinute, 60000);
})();