// ==UserScript==
// @name YouTube Enhancer (Stats)
// @description Add a Stats Button.
// @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
// @version 1.6
// @author exyezed
// @namespace https://github.com/exyezed/youtube-enhancer/
// @supportURL https://github.com/exyezed/youtube-enhancer/issues
// @license MIT
// @match https://www.youtube.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const styles = `
.videoStats {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 8px;
}
html[dark] .videoStats {
background-color: #ffffff1a;
}
html:not([dark]) .videoStats {
background-color: #0000000d;
}
html[dark] .videoStats:hover {
background-color: #ffffff33;
}
html:not([dark]) .videoStats:hover {
background-color: #00000014;
}
.videoStats svg {
width: 18px;
height: 18px;
}
html[dark] .videoStats svg {
fill: var(--yt-spec-text-primary, #fff);
}
html:not([dark]) .videoStats svg {
fill: var(--yt-spec-text-primary, #030303);
}
.shortsStats {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16px;
width: 48px;
height: 48px;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s;
}
html[dark] .shortsStats {
background-color: rgba(255, 255, 255, 0.1);
}
html:not([dark]) .shortsStats {
background-color: rgba(0, 0, 0, 0.05);
}
html[dark] .shortsStats:hover {
background-color: rgba(255, 255, 255, 0.2);
}
html:not([dark]) .shortsStats:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.shortsStats svg {
width: 24px;
height: 24px;
}
html[dark] .shortsStats svg {
fill: white;
}
html:not([dark]) .shortsStats svg {
fill: black;
}
.stats-menu-container {
position: relative;
display: inline-block;
}
.stats-horizontal-menu {
position: absolute;
display: flex;
left: 100%;
top: 0;
height: 100%;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.2s linear;
z-index: 100;
}
.stats-menu-container:hover .stats-horizontal-menu {
visibility: visible;
opacity: 1;
}
.stats-menu-button {
margin-left: 8px;
white-space: nowrap;
}
`;
let previousUrl = location.href;
let isChecking = false;
let channelFeatures = {
hasStreams: false,
hasShorts: false
};
function addStyles() {
if (!document.querySelector('#youtube-enhancer-styles')) {
const styleElement = document.createElement('style');
styleElement.id = 'youtube-enhancer-styles';
styleElement.textContent = styles;
document.head.appendChild(styleElement);
}
}
function getCurrentVideoUrl() {
const url = window.location.href;
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get('v');
if (videoId) {
return `https://www.youtube.com/watch?v=${videoId}`;
}
const shortsMatch = url.match(/\/shorts\/([^?]+)/);
if (shortsMatch) {
return `https://www.youtube.com/shorts/${shortsMatch[1]}`;
}
return null;
}
function getChannelIdentifier() {
const url = window.location.href;
let identifier = '';
if (url.includes('/channel/')) {
identifier = url.split('/channel/')[1].split('/')[0];
} else if (url.includes('/@')) {
identifier = url.split('/@')[1].split('/')[0];
}
return identifier;
}
async function checkChannelTabs(url) {
if (isChecking) return;
isChecking = true;
try {
const response = await fetch(url, {
credentials: 'same-origin'
});
if (!response.ok) {
isChecking = false;
return;
}
const html = await response.text();
const match = html.match(/var ytInitialData = (.+?);<\/script>/);
if (!match || !match[1]) {
isChecking = false;
return;
}
const data = JSON.parse(match[1]);
const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
let hasStreams = false;
let hasShorts = false;
tabs.forEach(tab => {
const tabUrl = tab?.tabRenderer?.endpoint?.commandMetadata?.webCommandMetadata?.url;
if (tabUrl) {
if (/\/streams$/.test(tabUrl)) hasStreams = true;
if (/\/shorts$/.test(tabUrl)) hasShorts = true;
}
});
channelFeatures = {
hasStreams: hasStreams,
hasShorts: hasShorts
};
const existingMenu = document.querySelector('.stats-menu-container');
if (existingMenu) {
existingMenu.remove();
createStatsMenu();
}
} catch (e) {
} finally {
isChecking = false;
}
}
function isChannelPage(url) {
return url.includes('youtube.com/') &&
(url.includes('/channel/') || url.includes('/@')) &&
!url.includes('/video/') &&
!url.includes('/watch');
}
function checkUrlChange() {
const currentUrl = location.href;
if (currentUrl !== previousUrl) {
previousUrl = currentUrl;
if (isChannelPage(currentUrl)) {
setTimeout(() => checkChannelTabs(currentUrl), 500);
}
}
}
function createStatsIcon(isShorts = false) {
const icon = document.createElement('div');
icon.className = isShorts ? 'shortsStats' : 'videoStats';
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 512 512");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z");
svg.appendChild(path);
icon.appendChild(svg);
icon.addEventListener('click', redirectToStatsAPI);
return icon;
}
function redirectToStatsAPI() {
const videoUrl = getCurrentVideoUrl();
if (videoUrl) {
const apiUrl = `https://afkarxyzstats.vercel.app/?directVideo=${encodeURIComponent(videoUrl)}`;
window.open(apiUrl, '_blank');
}
}
function insertIconForRegularVideo() {
const targetSelector = '#owner';
const target = document.querySelector(targetSelector);
if (target && !document.querySelector('.videoStats')) {
const statsIcon = createStatsIcon();
target.appendChild(statsIcon);
}
}
function insertIconForShorts() {
const shortsContainer = document.querySelector('ytd-reel-video-renderer[is-active] #actions');
if (shortsContainer && !shortsContainer.querySelector('.shortsStats')) {
const iconDiv = createStatsIcon(true);
shortsContainer.insertBefore(iconDiv, shortsContainer.firstChild);
return true;
}
return false;
}
function createButton(text, svgPath, viewBox, className, onClick) {
const buttonViewModel = document.createElement('button-view-model');
buttonViewModel.className = `yt-spec-button-view-model ${className}-view-model`;
const button = document.createElement('button');
button.className = `yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment ${className}-button`;
button.setAttribute('aria-disabled', 'false');
button.setAttribute('aria-label', text);
button.style.display = 'flex';
button.style.alignItems = 'center';
button.style.justifyContent = 'center';
button.style.gap = '8px';
button.addEventListener('click', onClick);
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", viewBox);
svg.style.width = "20px";
svg.style.height = "20px";
svg.style.fill = "currentColor";
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", svgPath);
svg.appendChild(path);
const buttonText = document.createElement('div');
buttonText.className = `yt-spec-button-shape-next__button-text-content ${className}-text`;
buttonText.textContent = text;
buttonText.style.display = 'flex';
buttonText.style.alignItems = 'center';
const touchFeedback = document.createElement('yt-touch-feedback-shape');
touchFeedback.style.borderRadius = 'inherit';
const touchFeedbackDiv = document.createElement('div');
touchFeedbackDiv.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
touchFeedbackDiv.setAttribute('aria-hidden', 'true');
const strokeDiv = document.createElement('div');
strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';
const fillDiv = document.createElement('div');
fillDiv.className = 'yt-spec-touch-feedback-shape__fill';
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
button.appendChild(svg);
button.appendChild(buttonText);
button.appendChild(touchFeedback);
buttonViewModel.appendChild(button);
return buttonViewModel;
}
function createStatsMenu() {
if (document.querySelector('.stats-menu-container')) {
return;
}
const containerDiv = document.createElement('div');
containerDiv.className = 'yt-flexible-actions-view-model-wiz__action stats-menu-container';
const mainButtonViewModel = document.createElement('button-view-model');
mainButtonViewModel.className = 'yt-spec-button-view-model main-stats-view-model';
const mainButton = document.createElement('button');
mainButton.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment main-stats-button';
mainButton.setAttribute('aria-disabled', 'false');
mainButton.setAttribute('aria-label', 'Stats');
mainButton.style.display = 'flex';
mainButton.style.alignItems = 'center';
mainButton.style.justifyContent = 'center';
mainButton.style.gap = '8px';
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 512 512");
svg.style.width = "20px";
svg.style.height = "20px";
svg.style.fill = "currentColor";
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z");
svg.appendChild(path);
const buttonText = document.createElement('div');
buttonText.className = 'yt-spec-button-shape-next__button-text-content main-stats-text';
buttonText.textContent = 'Stats';
buttonText.style.display = 'flex';
buttonText.style.alignItems = 'center';
const touchFeedback = document.createElement('yt-touch-feedback-shape');
touchFeedback.style.borderRadius = 'inherit';
const touchFeedbackDiv = document.createElement('div');
touchFeedbackDiv.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
touchFeedbackDiv.setAttribute('aria-hidden', 'true');
const strokeDiv = document.createElement('div');
strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';
const fillDiv = document.createElement('div');
fillDiv.className = 'yt-spec-touch-feedback-shape__fill';
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
mainButton.appendChild(svg);
mainButton.appendChild(buttonText);
mainButton.appendChild(touchFeedback);
mainButtonViewModel.appendChild(mainButton);
containerDiv.appendChild(mainButtonViewModel);
const horizontalMenu = document.createElement('div');
horizontalMenu.className = 'stats-horizontal-menu';
const channelButtonContainer = document.createElement('div');
channelButtonContainer.className = 'stats-menu-button channel-stats-container';
const channelButton = createButton(
'Channel',
"M64 48c-8.8 0-16 7.2-16 16l0 288c0 8.8 7.2 16 16 16l512 0c8.8 0 16-7.2 16-16l0-288c0-8.8-7.2-16-16-16L64 48zM0 64C0 28.7 28.7 0 64 0L576 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 416c-35.3 0-64-28.7-64-64L0 64zM120 464l400 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-400 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z",
"0 0 640 512",
'channel-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
const url = `https://afkarxyzstats.vercel.app/?directChannel=${channelId}`;
window.open(url, '_blank');
}
}
);
channelButtonContainer.appendChild(channelButton);
horizontalMenu.appendChild(channelButtonContainer);
if (channelFeatures.hasStreams) {
const liveButtonContainer = document.createElement('div');
liveButtonContainer.className = 'stats-menu-button live-stats-container';
const liveButton = createButton(
'Live',
"M99.8 69.4c10.2 8.4 11.6 23.6 3.2 33.8C68.6 144.7 48 197.9 48 256s20.6 111.3 55 152.8c8.4 10.2 7 25.3-3.2 33.8s-25.3 7-33.8-3.2C24.8 389.6 0 325.7 0 256S24.8 122.4 66 72.6c8.4-10.2 23.6-11.6 33.8-3.2zm376.5 0c10.2-8.4 25.3-7 33.8 3.2c41.2 49.8 66 113.8 66 183.4s-24.8 133.6-66 183.4c-8.4 10.2-23.6 11.6-33.8 3.2s-11.6-23.6-3.2-33.8c34.3-41.5 55-94.7 55-152.8s-20.6-111.3-55-152.8c-8.4-10.2-7-25.3 3.2-33.8zM248 256a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zm-61.1-78.5C170 199.2 160 226.4 160 256s10 56.8 26.9 78.5c8.1 10.5 6.3 25.5-4.2 33.7s-25.5 6.3-33.7-4.2c-23.2-29.8-37-67.3-37-108s13.8-78.2 37-108c8.1-10.5 23.2-12.3 33.7-4.2s12.3 23.2 4.2 33.7zM427 148c23.2 29.8 37 67.3 37 108s-13.8 78.2-37 108c-8.1 10.5-23.2 12.3-33.7 4.2s-12.3-23.2-4.2-33.7C406 312.8 416 285.6 416 256s-10-56.8-26.9-78.5c-8.1-10.5-6.3-25.5 4.2-33.7s25.5-6.3 33.7 4.2z",
"0 0 576 512",
'live-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
const url = `https://afkarxyzstats.vercel.app/?directStream=${channelId}`;
window.open(url, '_blank');
}
}
);
liveButtonContainer.appendChild(liveButton);
horizontalMenu.appendChild(liveButtonContainer);
}
if (channelFeatures.hasShorts) {
const shortsButtonContainer = document.createElement('div');
shortsButtonContainer.className = 'stats-menu-button shorts-stats-container';
const shortsButton = createButton(
'Shorts',
"M80 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l224 0c8.8 0 16-7.2 16-16l0-384c0-8.8-7.2-16-16-16L80 48zM16 64C16 28.7 44.7 0 80 0L304 0c35.3 0 64 28.7 64 64l0 384c0 35.3-28.7 64-64 64L80 512c-35.3 0-64-28.7-64-64L16 64zM160 400l64 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-64 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z",
"0 0 384 512",
'shorts-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
const url = `https://afkarxyzstats.vercel.app/?directShorts=${channelId}`;
window.open(url, '_blank');
}
}
);
shortsButtonContainer.appendChild(shortsButton);
horizontalMenu.appendChild(shortsButtonContainer);
}
containerDiv.appendChild(horizontalMenu);
const joinButton = document.querySelector('.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)');
if (joinButton) {
joinButton.parentNode.appendChild(containerDiv);
} else {
const buttonContainer = document.querySelector('#subscribe-button + #buttons');
if (buttonContainer) {
buttonContainer.appendChild(containerDiv);
}
}
return containerDiv;
}
function checkAndAddMenu() {
const joinButton = document.querySelector('.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)');
const statsMenu = document.querySelector('.stats-menu-container');
if (joinButton && !statsMenu) {
createStatsMenu();
}
}
function checkAndInsertIcon() {
const isShorts = window.location.pathname.includes('/shorts/');
if (isShorts) {
const shortsObserver = new MutationObserver((_mutations, observer) => {
if (insertIconForShorts()) {
observer.disconnect();
}
});
const shortsContainer = document.querySelector('ytd-shorts');
if (shortsContainer) {
shortsObserver.observe(shortsContainer, {
childList: true,
subtree: true
});
insertIconForShorts();
}
} else if (getCurrentVideoUrl()) {
insertIconForRegularVideo();
}
}
function init() {
addStyles();
checkAndInsertIcon();
checkAndAddMenu();
history.pushState = (function(f) {
return function() {
const result = f.apply(this, arguments);
checkUrlChange();
return result;
};
})(history.pushState);
history.replaceState = (function(f) {
return function() {
const result = f.apply(this, arguments);
checkUrlChange();
return result;
};
})(history.replaceState);
window.addEventListener('popstate', checkUrlChange);
if (isChannelPage(location.href)) {
checkChannelTabs(location.href);
}
}
const observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === 'childList') {
checkAndInsertIcon();
checkAndAddMenu();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.addEventListener('yt-navigate-finish', () => {
checkAndInsertIcon();
checkAndAddMenu();
if (isChannelPage(location.href)) {
checkChannelTabs(location.href);
}
});
document.addEventListener('yt-action', function(event) {
if (event.detail && event.detail.actionName === 'yt-reload-continuation-items-command') {
checkAndInsertIcon();
checkAndAddMenu();
}
});
})();