// ==UserScript==
// @name Live Chat on YouTube Mobile (Auto Load + Auto Recreate)
// @version 1.4
// @description Auto-loads YouTube Live Chat on mobile.
// @match https://m.youtube.com/*
// @grant none
// @license MIT
// @namespace https://greasyfork.org/users/1360319
// ==/UserScript==
(function() {
'use strict';
// --- Chat Container ---
const chatContainer = document.createElement('div');
Object.assign(chatContainer.style, {
display: 'none',
position: 'fixed',
width: '100%',
top: '250px',
bottom: '0',
height:'auto',
left: '0',
right: '0',
borderTop: '1px solid #555',
backgroundColor: '#333',
zIndex: '9998',
overflow: 'hidden'
});
// --- Loader Spinner ---
const loader = document.createElement('div');
Object.assign(loader.style, {
border: '4px solid #555',
borderTop: '4px solid #ff0000',
borderRadius: '50%',
width: '30px',
height: '30px',
animation: 'spin 1s linear infinite',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: '10000',
display: 'none'
});
const styleSheet = document.createElement("style");
styleSheet.textContent = `
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.YtmBottomSheetOverlayRendererOverlayContainer{opacity:0!important}
bottom-sheet-container{
z-index:55000!important;
}
#ytm-chat-top-toggle {
position: absolute;
top: 10px;
left: 50%;
background:#121212;
transform: translateX(-50%);
background: transparent;
color: grey;
border: none;
border-radius: 6px;
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
z-index: 10001;
touch-action: manipulation;
}
`;
document.head.appendChild(styleSheet);
// --- Add top-center toggle button ---
const topToggleBtn = document.createElement('button');
topToggleBtn.id = 'ytm-chat-top-toggle';
topToggleBtn.textContent = 'Hide Chat';
chatContainer.appendChild(topToggleBtn);
chatContainer.appendChild(loader);
document.body.appendChild(chatContainer);
// --- State ---
let currentVideoId,chatButton,lastVideoId,chatIframe,viewerInterval,buttonCheckInterval,streamTimeSpan,actualStartTime,streamTimeInterval,ti= null;
let ii=false;
const showLoader = () => {
loader.style.display = 'block';
chatContainer.style.backgroundColor = '#111';
};
const hideLoader = () => {
loader.style.display = 'none';
chatContainer.style.backgroundColor = '#333';
};
// --- Create and insert "Currently Watching" and "Hide Chat" buttons ---
const ensureChatButtons = () => {
const controlsTop = document.querySelector('.player-controls-top');
if (controlsTop) {
chatButton = document.getElementById('ytm-hide-chat-btn');
// Create Hide/Display Chat button
if (!chatButton) {
chatButton = document.createElement('button');
chatButton.id = 'ytm-hide-chat-btn';
chatButton.textContent = '💬';
chatButton.style.zIndex = '5555';
Object.assign(chatButton.style, {
color: '#fff',
border: 'none',
opacity:'0.7',
display:'none',
borderRadius: '4px',
padding: '4px 6px',
marginRight: '6px',
touchAction: 'manipulation',
cursor: 'pointer',
fontSize: '20px'
});
}
if (!document.getElementById('ytm-hide-chat-btn')) {
controlsTop.insertBefore(chatButton, controlsTop.firstChild);
chatButton.addEventListener('click', toggleChatVisibility, { passive: true });
chatButton.addEventListener('touchstart', toggleChatVisibility, { passive: true });
topToggleBtn.addEventListener('click', toggleChatVisibility, { passive: true });
topToggleBtn.addEventListener('touchstart', toggleChatVisibility, { passive: true });
}
}
};
// --- Unified toggle function ---
const toggleChatVisibility = () => {
if(!ii){
ii=true;
chatButton = document.getElementById('ytm-hide-chat-btn');
const hidden = chatContainer.style.visibility === 'hidden';
if (hidden) {
chatContainer.style.visibility = 'visible';
if (chatButton) {
chatButton.style.display='none';
}
} else {
//avoid accidental touches/clicks
const blocker = document.createElement('div');
blocker.style.position = 'fixed';
blocker.style.top = 0;
blocker.style.left = 0;
blocker.style.width = '100%';
blocker.style.height = '100%';
blocker.style.background = 'transparent';
blocker.style.zIndex = 999999;
blocker.style.pointerEvents = 'auto';
document.body.appendChild(blocker);
setTimeout(() => blocker.remove(), 200);
chatContainer.style.visibility = 'hidden';
if (chatButton)
{
chatButton.style.display='';
}
}
clearTimeout(ti);
ti= setTimeout(() => ii=false, 200);
}
};
// --- Create chat iframe ---
const createChatIframe = async (videoId) => {
if (chatIframe && chatIframe.parentElement) chatIframe.remove();
chatIframe = document.createElement('iframe');
Object.assign(chatIframe.style, {
width: '100%',
height: '100%',
border: 'none',
maxWidth: '100%',
opacity: '0'
});
chatIframe.src = `https://www.youtube.com/live_chat?v=${videoId}&embed_domain=${window.location.hostname}`;
chatIframe.onload = () => {
hideLoader();
chatIframe.style.opacity = '1';
};
if(chatButton) chatButton.style.display='none';
chatContainer.appendChild(chatIframe);
currentVideoId = videoId;
// Clear old interval if any
[ buttonCheckInterval, streamTimeInterval].forEach(i => { if (i) clearInterval(i); });
buttonCheckInterval = streamTimeInterval = null;
ensureChatButtons();
if (!buttonCheckInterval) {
buttonCheckInterval = setInterval(ensureChatButtons, 1000);
}
};
// --- Close chat and cleanup ---
const closeChat = () => {
lastVideoId = null;
[buttonCheckInterval, streamTimeInterval].forEach(i => clearInterval(i));
viewerInterval = buttonCheckInterval = streamTimeInterval = null;
if (chatContainer.style.display !== 'none') {
if (chatIframe && chatIframe.parentElement) chatIframe.remove();
chatIframe = null;
chatContainer.style.display = 'none';
hideLoader();
}
if (streamTimeSpan && streamTimeSpan.parentElement) {
streamTimeSpan.remove();
streamTimeSpan = null;
}
};
// --- Check if current page is a live video ---
const isVideoPage = () => {
return (
window.location.pathname === '/watch' &&
window.location.search.includes('v=') &&
(document.querySelector('.ytwPlayerTimeDisplayLiveDot.ytwPlayerTimeDisplayPill > div > span > .yt-core-attributed-string')?.textContent.includes('live') ||
document.querySelector('.secondary-text > .yt-core-attributed-string')?.textContent.includes('watching now') ||
document.querySelector('.secondary-text > .yt-core-attributed-string')?.textContent.includes('en direct') ||
document.querySelector('.secondary-text > .yt-core-attributed-string')?.textContent.includes('en directo') ||
document.querySelector('.secondary-text > .yt-core-attributed-string')?.textContent.includes('en vivo') ||
document.querySelector('.secondary-text > .yt-core-attributed-string')?.textContent.includes('em direto'))
);
};
// --- Auto-load chat ---
const autoLoadChat = () => {
if (isVideoPage() && document.querySelector('#player') && document.querySelector('video')) {
if(document.querySelector('#player').getBoundingClientRect().bottom>100) chatContainer.style.top = `${document.querySelector('#player').getBoundingClientRect().bottom}px`;
const videoIdMatch = window.location.search.match(/v=([^&]+)/);
if (!videoIdMatch) return;
const videoId = videoIdMatch[1];
showLoader();
createChatIframe(videoId);
chatContainer.style.display = 'block';
chatContainer.style.visibility = 'visible';
} else {
closeChat();
}
};
// --- Monitor video changes ---
setInterval(() => {
// remove "ready to shop" banner
document.querySelector('.YtmBottomSheetOverlayRendererHeader')?.children[1]?.children[0]?.click();
if (document.body.hasAttribute('bottom-sheet-open')) {
document.body.removeAttribute('bottom-sheet-open');
}
const videoIdMatch = window.location.search.match(/v=([^&]+)/);
const newVideoId = videoIdMatch ? videoIdMatch[1] : null;
if (isVideoPage()) {
if (chatContainer.style.display === 'none' || newVideoId !== currentVideoId) {
autoLoadChat();
}
const videoPlayer = document.querySelector('#player');
if (videoPlayer) {
const rect = videoPlayer.getBoundingClientRect();
if(rect.bottom>100) chatContainer.style.top = `${rect.bottom}px`;
}
} else {
closeChat();
currentVideoId = null;
}
}, 1000);
autoLoadChat();
})();