Kick Chat Overlay

chat overlay on Kick.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Kick Chat Overlay
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  chat overlay on Kick.
// @author       korip1
// @license MIT
// @match        https://kick.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .chat-overlay-item {
            position: absolute;
            white-space: nowrap;
            font-weight: bold;
            color: white;
            text-shadow: 2px 2px 3px black;
            z-index: 9999;
            pointer-events: none;
            will-change: transform;
        }
        .chat-overlay-item img {
            height: 1.3em; /* 글자 크기에 맞춰 이모티콘 높이 조절 */
            vertical-align: middle; /* 텍스트와 수직 중앙 정렬 */
            margin: 0 0.1em; /* 이모티콘 좌우에 약간의 여백 추가 */
        }
    `);

    let fontSize = GM_getValue('fontSize', 24);
    let scrollDuration = GM_getValue('scrollDuration', 15);
    let maxChats = GM_getValue('maxChats', 20);
    let chatAreaHeight = GM_getValue('chatAreaHeight', 80);

    GM_registerMenuCommand('자막 크기 설정', () => {
        const newSize = prompt('자막의 글씨 크기를 입력하세요 (현재: ' + fontSize + 'px)', fontSize);
        if (newSize && !isNaN(newSize)) {
            fontSize = parseInt(newSize, 10);
            GM_setValue('fontSize', fontSize);
            alert('글씨 크기가 ' + fontSize + 'px로 설정되었습니다.');
        }
    });

    GM_registerMenuCommand('자막 속도 설정 (초)', () => {
        const newDuration = prompt('자막이 지나가는 시간을 초 단위로 입력하세요 (현재: ' + scrollDuration + '초)\n(숫자가 클수록 느려집니다)', scrollDuration);
        if (newDuration && !isNaN(newDuration)) {
            scrollDuration = parseFloat(newDuration);
            GM_setValue('scrollDuration', scrollDuration);
            alert('자막 속도가 ' + scrollDuration + '초로 설정되었습니다.');
        }
    });

    GM_registerMenuCommand('최대 자막 수 설정', () => {
        const newMax = prompt('화면에 동시에 표시할 최대 자막 개수를 입력하세요 (현재: ' + maxChats + '개)\n(0으로 설정하면 무제한)', maxChats);
        if (newMax !== null && !isNaN(newMax)) {
            maxChats = parseInt(newMax, 10);
            GM_setValue('maxChats', maxChats);
            alert('최대 자막 수가 ' + (maxChats === 0 ? '무제한' : maxChats + '개') + '로 설정되었습니다.');
        }
    });

        GM_registerMenuCommand('자막 표시 영역 설정 (%)', () => {
        const newArea = prompt('자막이 표시될 상단 영역을 %로 입력하세요 (현재: ' + chatAreaHeight + '%)\n(예: 80 입력 시 하단 20%는 빈 공간으로 유지)', chatAreaHeight);
        if (newArea && !isNaN(newArea) && newArea > 0 && newArea <= 100) {
            chatAreaHeight = parseInt(newArea, 10);
            GM_setValue('chatAreaHeight', chatAreaHeight);
            alert('자막 표시 영역이 상단 ' + chatAreaHeight + '%로 설정되었습니다.');
        }
    });

    let overlayBox = null;
    let playerParent = null;

    function showChat(messageHTML) {
        if (!playerParent || !overlayBox) return;
        if (maxChats > 0 && overlayBox.childElementCount >= maxChats) {
            return;
        }

        const video = playerParent.querySelector('video');
        if (!video) return;

        const chatEl = document.createElement('div');
        chatEl.className = 'chat-overlay-item';

        chatEl.innerHTML = messageHTML;

        chatEl.style.fontSize = `${fontSize}px`;
        chatEl.style.top = `${Math.random() * chatAreaHeight}%`;
        chatEl.style.left = `${video.offsetWidth}px`;
        chatEl.style.transition = `transform ${scrollDuration}s linear`;

        overlayBox.appendChild(chatEl);

        setTimeout(() => {
            const endPosition = video.offsetWidth + chatEl.offsetWidth + 20;
            chatEl.style.transform = `translateX(-${endPosition}px)`;
        }, 10);

        setTimeout(() => {
            chatEl.remove();
        }, scrollDuration * 1000);
    }

    function init(targetNode) {
        playerParent = targetNode;
        playerParent.style.position = 'relative';

        overlayBox = document.createElement('div');
        overlayBox.id = 'custom-chat-overlay';
        Object.assign(overlayBox.style, {
            position: 'absolute', top: '0', left: '0',
            width: '100%', height: '100%',
            zIndex: '10', pointerEvents: 'none'
        });
        playerParent.appendChild(overlayBox);

        document.addEventListener('fullscreenchange', () => {
            const fsEl = document.fullscreenElement;
            if (fsEl) {
                fsEl.appendChild(overlayBox);
            } else if (playerParent) {
                playerParent.appendChild(overlayBox);
            }
        });

        const chatContainer = document.querySelector('#chatroom-messages');
        if (chatContainer) {
            const observer = new MutationObserver((mutationsList) => {
                for (const mutation of mutationsList) {
                    if (mutation.addedNodes.length) {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType !== 1) return;
                            const msgEl = node.querySelector('span.font-normal');
                            if (msgEl) {
                                showChat(msgEl.innerHTML);
                            }
                        });
                    }
                }
            });
            observer.observe(chatContainer, { childList: true, subtree: true });
        }
    }

    const bootInterval = setInterval(() => {
        const target = document.querySelector('#injected-channel-player');
        if (target) {
            clearInterval(bootInterval);
            init(target);
        }
    }, 500);

})();