Chzzk_L&V: Chatting Plus

"파트너이며 매니저가 아닌 스트리머"의 닉네임과 메시지를 연두색으로 표시, 채팅 닉네임 꾸미기 효과 중 스텔스모드 무력화 및 형광펜 제거, 긴 닉네임 10자 초과 시 생략 처리, 타임머신 기능 안내 및 치트키 구매 팝업 클릭하여 닫기

目前為 2025-04-16 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Chzzk_L&V: Chatting Plus
// @namespace    Chzzk_Live&VOD: Chatting Plus
// @version      1.5
// @description  "파트너이며 매니저가 아닌 스트리머"의 닉네임과 메시지를 연두색으로 표시, 채팅 닉네임 꾸미기 효과 중 스텔스모드 무력화 및 형광펜 제거, 긴 닉네임 10자 초과 시 생략 처리, 타임머신 기능 안내 및 치트키 구매 팝업 클릭하여 닫기
// @author       DOGJIP
// @match        https://chzzk.naver.com/*
// @grant        none
// @run-at       document-end
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chzzk.naver.com
// ==/UserScript==

(function() {
    'use strict';

    const LIGHT_GREEN = "rgb(102,200,102)";
    let chatObserver = null;

    // 유틸: 닉네임 색상이 너무 어두운 경우 스타일 제거
    // (예: color가 rgba(0, 0, 0, 0)인 경우 brightness가 0이므로 해당 스타일을 초기화)
    function fixUnreadableNicknameColor(nicknameElem) {
        if (!nicknameElem) return;
        const computedColor = window.getComputedStyle(nicknameElem).color;
        const rgbaMatch = computedColor.match(/rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?([0-9.]+))?\)/);
        if (!rgbaMatch) return;

        const r = parseInt(rgbaMatch[1], 10);
        const g = parseInt(rgbaMatch[2], 10);
        const b = parseInt(rgbaMatch[3], 10);
        const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1;
        const brightness = (r * 299 + g * 587 + b * 114) / 1000;
        const visibility = brightness * a;

        if (visibility < 50) {
            nicknameElem.style.color = ''; // 기본색으로 복원
        }
    }

    // 유틸: 닉네임 백그라운드 색상 제거
    function removeBackgroundColor(nicknameElem) {
        if (!nicknameElem) return;
        const bgTarget = nicknameElem.querySelector('[style*="background-color"]');
        if (bgTarget) {
            bgTarget.style.removeProperty('background-color');
        }
    }

    // 유틸: 닉네임이 너무 길면 자르고 ... 추가
    function truncateNickname(nicknameElem, maxLen = 10) {
        if (!nicknameElem) return;
        const textSpan = nicknameElem.querySelector('.name_text__yQG50');
        if (!textSpan) return;
        const fullText = textSpan.textContent;
        if (fullText.length > maxLen) {
            textSpan.textContent = fullText.slice(0, maxLen) + '...';
        }
    }

        // 치트키 구매시 타임머신 사용가능 툴팁의 닫기 버튼을 클릭하는 함수
    function autoClickTooltipCloseButton() {
        const closeButton = document.querySelector('.cheat_key_tooltip_button_close__QrFhG');

        // 버튼이 있으면 클릭
        if (closeButton) {
            closeButton.click(); // 툴팁 닫기 버튼 클릭
        }
    }

    function processChatMessage(messageElem) {
        if (messageElem.getAttribute('data-partner-processed') === 'true') return;

        // (기존 파트너 판별 조건은 그대로 유지)
        const isPartner = messageElem.querySelector('[class*="name_icon__zdbVH"]') !== null;

        // 배지 검사 (매니저, 스트리머 여부)
        const badgeImgs = messageElem.querySelectorAll('.badge_container__a64XB img');
        let isManager = false;
        let isStreamer = false;
        badgeImgs.forEach(img => {
            if (img.src.includes("manager.png")) isManager = true;
            if (img.src.includes("streamer.png")) isStreamer = true;
        });

        const nicknameElem = messageElem.querySelector('.live_chatting_username_nickname__dDbbj');
        const textElem = messageElem.querySelector('.live_chatting_message_text__DyleH');

        // 공통 처리: 가독성 개선, 백그라운드 제거, 긴 닉네임 자르기
        fixUnreadableNicknameColor(nicknameElem);
        removeBackgroundColor(nicknameElem);
        truncateNickname(nicknameElem, 10);

        // 조건: 파트너 스트리머이며, 매니저와 스트리머 본인이 아닐 때만 연두색 적용
        if (isPartner && !isManager && !isStreamer) {
            if (nicknameElem) nicknameElem.style.color = LIGHT_GREEN;
            if (textElem) textElem.style.color = LIGHT_GREEN;
        }

        messageElem.setAttribute('data-partner-processed', 'true');
    }

        // 툴팁 닫기 기능을 위한 MutationObserver 설정
    function setupTooltipObserver() {
        const tooltipObserver = new MutationObserver(() => {
            // 툴팁이 생성될 때마다 자동으로 닫기 버튼 클릭
            autoClickTooltipCloseButton();
        });

        // 툴팁이 포함된 요소가 있을 때만 감지
        const tooltipContainer = document.querySelector('.tooltip');
        if (tooltipContainer) {
            tooltipObserver.observe(tooltipContainer, { childList: true, subtree: true });
        }
    }

    // 채팅 감지 및 처리
    function setupChatObserver() {
        if (chatObserver) chatObserver.disconnect();
        // live와 vod 모두 지원하도록 채팅 컨테이너 선택자 수정
        const chatContainer = document.querySelector('[class*="live_chatting_list_wrapper__"], [class*="vod_chatting_list__"]');
        if (!chatContainer) {
            setTimeout(setupChatObserver, 500);
            return;
        }

        // 기존 메시지 처리
        const existingMessages = chatContainer.querySelectorAll('[class^="live_chatting_message_chatting_message__"]');
        existingMessages.forEach(msg => processChatMessage(msg));

        // 신규 메시지 감지 (MutationObserver)
        chatObserver = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType !== 1) return;
                    if (node.className?.includes('live_chatting_message_chatting_message__')) {
                        processChatMessage(node);
                    } else {
                        node.querySelectorAll?.('[class^="live_chatting_message_chatting_message__"]').forEach(processChatMessage);
                    }
                });
            });
        });

        chatObserver.observe(chatContainer, { childList: true, subtree: true });
    }

    function setupSPADetection() {
        let lastUrl = location.href;
        const onUrlChange = () => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                setTimeout(setupChatObserver, 1000);
            }
        };
        ['pushState','replaceState'].forEach(method => {
            const original = history[method];
            history[method] = function (...args) {
                original.apply(this, args);
                onUrlChange();
            };
        });
        window.addEventListener("popstate", onUrlChange);
    }

    function init() {
        setupChatObserver();
        setupSPADetection();
        setupTooltipObserver();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();