Live Chat on YouTube Mobile

Integrates the live chat into the mobile version of YouTube

当前为 2025-09-30 提交的版本,查看 最新版本

// ==UserScript==
// @name Live Chat on YouTube Mobile
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Integrates the live chat into the mobile version of YouTube
// @match https://m.youtube.com/*
// @grant none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

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

    document.body.appendChild(button);

    const chatContainer = document.createElement('div');
    chatContainer.style.display = 'none';
    chatContainer.style.position = 'fixed';
    chatContainer.style.width = '100%';
    chatContainer.style.top = '250px';
    chatContainer.style.bottom = '0';
    chatContainer.style.height = 'auto';
    chatContainer.style.left = '0';
    chatContainer.style.right = '0';
    chatContainer.style.borderTop = '1px solid #ccc';
    chatContainer.style.backgroundColor = '#fff';
    chatContainer.style.zIndex = '9998';
    chatContainer.style.overflow = 'auto';

    const chatIframe = document.createElement('iframe');
    chatIframe.style.width = '100%';
    chatIframe.style.height = '100%';
    chatIframe.style.border = 'none';
    chatIframe.style.maxWidth = '100%';

    let isIframeLoaded = false;
    let observer = null;
    let currentVideoId = null;

    const closeChat = () => {
        if (chatContainer.style.display !== 'none') {
            chatContainer.style.display = 'none';
            button.innerText = '💬';
        }
        if (observer) {
            observer.disconnect();
            observer = null;
        }
    };

    const isVideoPage = () => {
        return window.location.pathname === '/watch' && window.location.search.includes('v=');
    };

    const applyIframeStyles = () => {
        if (!isIframeLoaded) {
            try {
                const iframeDocument = chatIframe.contentWindow.document;
                const style = iframeDocument.createElement('style');
                style.textContent = `
                    .style-scope.yt-live-chat-text-message-renderer {
                        font-size: 12px !important;
                    }
                    .style-scope.yt-live-chat-text-message-renderer #author-name {
                        font-size: 12px !important;
                    }
                `;
                iframeDocument.head.appendChild(style);
                isIframeLoaded = true;
            } catch (e) {
            }
        }
    };

    const startVideoObserver = () => {
        const videoElement = document.querySelector('video');
        if (!videoElement) return;

        const playerContainer = videoElement.closest('div');
        if (!playerContainer) return;

        if (observer) observer.disconnect();

        observer = new MutationObserver((mutationsList, obs) => {
            let wasRemoved = false;
            mutationsList.forEach(mutation => {
                mutation.removedNodes.forEach(node => {
                    if (node === playerContainer || (node.contains && node.contains(playerContainer))) {
                        wasRemoved = true;
                    }
                });
            });

            if (wasRemoved) {
                closeChat();
                obs.disconnect();
            }
        });

        const config = { childList: true, subtree: true };
        const parentOfPlayerContainer = playerContainer.parentNode;
        if (parentOfPlayerContainer) {
            observer.observe(parentOfPlayerContainer, config);
        }
    };


    button.onclick = function() {
        if (chatContainer.style.display === 'none') {
            const videoIdMatch = window.location.search.match(/v=([^&]+)/);
            if (videoIdMatch) {
                const videoId = videoIdMatch[1];

                if (!chatIframe.parentElement) {
                    chatContainer.appendChild(chatIframe);
                    document.body.appendChild(chatContainer);
                    chatIframe.onload = applyIframeStyles;
                }

                if (videoId !== currentVideoId) {
                    const newChatSrc = `https://www.youtube.com/live_chat?v=${videoId}&embed_domain=${window.location.hostname}`;

                    chatIframe.src = newChatSrc;
                    currentVideoId = videoId;
                    isIframeLoaded = false;
                }

                const videoPlayer = document.querySelector('video');
                if (videoPlayer) {
                    const videoRect = videoPlayer.getBoundingClientRect();

                    const isVertical = videoRect.height > videoRect.width && videoRect.height > 100;

                    if (isVertical) {
                        chatContainer.style.top = '50%';
                        chatContainer.style.height = '50%';
                    } else {
                        chatContainer.style.top = `${videoRect.bottom}px`;
                        chatContainer.style.height = 'auto';
                    }

                    chatContainer.style.bottom = '0'; 
                } else {
                    chatContainer.style.top = '250px';
                    chatContainer.style.height = 'auto';
                }

                chatContainer.style.display = 'block';

                button.innerText = '✖️';

                chatIframe.focus();

                startVideoObserver();

            } else {
                alert('No se pudo encontrar el ID del video.');
            }
        } else {
            closeChat();
        }
    };

    const updateButtonVisibility = () => {
        button.style.display = isVideoPage() ? 'block' : 'none';
        if (!isVideoPage()) {
            closeChat();
            currentVideoId = null;
        }

        if (chatContainer.style.display !== 'none') {
             const videoPlayer = document.querySelector('video');
             if (videoPlayer) {
                const videoRect = videoPlayer.getBoundingClientRect();

                const isVertical = videoRect.height > videoRect.width && videoRect.height > 100;
                if (isVertical) {
                    chatContainer.style.top = '50%';
                    chatContainer.style.height = '50%';
                } else {
                    chatContainer.style.top = `${videoRect.bottom}px`;
                    chatContainer.style.height = 'auto';
                }
            }
        }
    };

    setInterval(updateButtonVisibility, 500);
    updateButtonVisibility();

})();