Live Chat on YouTube Mobile

Integrates the live chat into the mobile version of YouTube

// ==UserScript==
// @name Live Chat on YouTube Mobile
// @version 1.3.1
// @description Integrates the live chat into the mobile version of YouTube
// @match https://m.youtube.com/*
// @grant none
// @namespace https://greasyfork.org/en/scripts/519614-live-chat-on-youtube-mobile
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // CONFIGURATION 
    const AUTO_HIDE_CHAT_BUTTON = true; // If true, the button only appears when a Live/Premiere is detected
    const AUTO_SHOW_CHAT = false; // If true, the chat automatically opens when a Live/Premiere loads.

    let currentVideoId = null, isIframeLoaded = false, observer = null, previousWidth = window.innerWidth, isChatVisible = false;
    const elements = {};

    const isVideoPage = () => window.location.pathname === '/watch' && window.location.search.includes('v=');
    const getVideoId = () => (window.location.search.match(/v=([^&]+)/) || [])[1];
    const isPortraitOrientation = () => window.innerHeight > window.innerWidth;
    
    const isLiveOrPremiere = () => {
        if (!AUTO_HIDE_CHAT_BUTTON) {
            return true;
        }

        const infoContainer = elements.infoContainer;
        if (infoContainer) {
            const content = infoContainer.textContent || '';
            return content.toLowerCase().includes('watching now') 
                || content.toLowerCase().includes('personas mirando ahora')
                || content.toLowerCase().includes('usuarios viéndolo ahora')
                || content.toLowerCase().includes('viewing now');
        }
        return false;
    };

    const showLoader = (darkbackground = '#111') => {
        elements.loader.style.display = 'block';
        elements.chatIframe.style.opacity = '0';
        elements.chatContainer.style.backgroundColor = darkbackground;
    };

    const hideLoader = () => {
        elements.loader.style.display = 'none';
        elements.chatIframe.style.opacity = '1';
        elements.chatContainer.style.backgroundColor = '#333';
    };

    const updateChatPosition = () => {
        const videoPlayer = elements.videoPlayer;
        if (videoPlayer) {
            const videoRect = videoPlayer.getBoundingClientRect();
            const isVerticalVideo = videoRect.height > videoRect.width && videoRect.height > 100;
            elements.chatContainer.style.top = isVerticalVideo ? '50%' : `${videoRect.bottom}px`;
            elements.chatContainer.style.height = isVerticalVideo ? '50%' : 'auto';
            elements.chatContainer.style.bottom = '0';
        } else {
            elements.chatContainer.style.top = '250px';
            elements.chatContainer.style.height = 'auto';
        }
    };

    const setButtonVisibility = (visible) => {
        elements.button.style.display = (visible && isPortraitOrientation() && isLiveOrPremiere()) ? 'block' : 'none';
    };

    const closeChat = () => {
        if (elements.chatContainer.style.display !== 'none') {
            elements.chatContainer.style.display = 'none';
            elements.button.innerText = '💬';
            document.body.classList.remove('chat-open-no-scroll');
            isChatVisible = false;
        }
    };
    
    const showChat = () => {
        if (!isIframeLoaded) {
            showLoader();
        } else {
            hideLoader();
        }
        
        updateChatPosition();
        elements.chatContainer.style.display = 'block';
        document.body.classList.add('chat-open-no-scroll');
        elements.button.innerText = '✖️';
        elements.chatIframe.focus();
        isChatVisible = true;
    };

    const toggleChat = () => {
        if (elements.button.style.display === 'none') return;
        
        if (!isChatVisible) {
            showChat();
        } else {
            closeChat();
        }
    };

    const preloadChatIframe = () => {
        const videoId = getVideoId();
        
        if (isVideoPage() && videoId && isLiveOrPremiere() && videoId !== currentVideoId) {
            currentVideoId = videoId;
            isIframeLoaded = false;
            
            elements.chatIframe.style.opacity = '0'; 
            
            const newChatSrc = `https://www.youtube.com/live_chat?v=${videoId}&embed_domain=${window.location.hostname}`;
            elements.chatIframe.src = newChatSrc;
            
        } else if (videoId === currentVideoId && isIframeLoaded) {
             setButtonVisibility(true);
        }
    };


    const updateDOMElementsCache = () => {
        elements.videoPlayer = document.querySelector('video');
        elements.infoContainer = document.querySelector('.slim-video-information-content');
    };

    const handlePageAndVideoState = () => {
        const onVideoPage = isVideoPage();
        const newVideoId = getVideoId();
        
        if (onVideoPage && newVideoId !== currentVideoId) {
            updateDOMElementsCache();
            preloadChatIframe(); 
            
            if (AUTO_SHOW_CHAT && isPortraitOrientation() && isLiveOrPremiere()) {
                showChat();
            }

        } else if (!onVideoPage) {
            elements.videoPlayer = null;
            elements.infoContainer = null;
            currentVideoId = null;
            isIframeLoaded = false;
            elements.chatIframe.src = '';
        }
        
        setButtonVisibility(onVideoPage);

        if (!onVideoPage) {
            closeChat();
        } else if (isChatVisible) {
            if (newVideoId && newVideoId !== currentVideoId) {
                closeChat(); 
            } else if (!isPortraitOrientation() || !isLiveOrPremiere()) {
                closeChat();
            } else {
                updateChatPosition(); 
            }
        }
    };

    const createUIElements = () => {
        elements.button = document.createElement('button');
        Object.assign(elements.button.style, {
            position: 'fixed', bottom: '64px', right: '20px', zIndex: '9999', padding: '10px',
            backgroundColor: '#ff0000', color: '#fff', border: 'none', borderRadius: '50%',
            width: '45px', height: '45px', fontSize: '20px', lineHeight: '0',
            boxShadow: '0 3px 6px rgba(0,0,0,0.3)', cursor: 'pointer', display: 'none'
        });
        elements.button.innerText = '💬';
        document.body.appendChild(elements.button);

        elements.chatContainer = document.createElement('div');
        Object.assign(elements.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'
        });
        document.body.appendChild(elements.chatContainer);

        elements.chatIframe = document.createElement('iframe');
        Object.assign(elements.chatIframe.style, {
            width: '100%', height: '100%', border: 'none', maxWidth: '100%', opacity: '0'
        });
        elements.chatContainer.appendChild(elements.chatIframe);

        elements.loader = document.createElement('div');
        Object.assign(elements.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'
        });
        elements.chatContainer.appendChild(elements.loader);

        const styleSheet = document.createElement("style");
        styleSheet.type = "text/css";
        styleSheet.innerText = `@keyframes spin { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } .chat-open-no-scroll { overflow: hidden !important; position: fixed !important; width: 100% !important; height: 100% !important; }`;
        document.head.appendChild(styleSheet);
    };

    const initializeObserver = () => {
        const targetNode = document.querySelector('body'); 
        if (targetNode) {
            observer = new MutationObserver(() => setTimeout(handlePageAndVideoState, 100));
            observer.observe(targetNode, { childList: true, subtree: true });
        }

        window.addEventListener('popstate', handlePageAndVideoState);
        window.addEventListener('resize', () => {
            const isRotationDetected = window.innerWidth !== previousWidth;
            if (isRotationDetected) handlePageAndVideoState(); 
            else if (isChatVisible) updateChatPosition();
            previousWidth = window.innerWidth;
        });
        handlePageAndVideoState();
    };

    const initialize = () => {
        if (window.self !== window.top) return;
        createUIElements();
        
        updateDOMElementsCache(); 
        
        elements.button.addEventListener('click', toggleChat);
        
        elements.chatIframe.onload = () => { 
            isIframeLoaded = true; 
            
            if (isChatVisible) {
                hideLoader(); 
            }
            
            setButtonVisibility(true);
        };
        
        preloadChatIframe(); 
        
        initializeObserver();
    };

    initialize();
})();