Chzzk_L&V: Chatting Plus

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

当前为 2025-05-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Chzzk_L&V: Chatting Plus
// @namespace    Chzzk_Live&VOD: Chatting Plus
// @version      1.8.1
// @description  "파트너이며 매니저가 아닌 유저" 또는 지정한 streamer 닉네임의 메시지를 연두색으로 표시, 채팅 닉네임 꾸미기 효과 중 스텔스모드 무력화 및 형광펜 제거, 긴 닉네임 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';

    // ===== 설정: 지정 스트리머 및 켜고끄고 싶은 기능을 수정할 수 있습니다. true/false ====================
    // ===== 사용자 지정 스트리머 리스트 (파트너가 아닌 스트리머의 이름을 아래의 예시처럼 정확히 넣기) =====
    let streamer = ['고수달', '냐 미 Nyami', '새 담', '청 묘', '침착맨', '삼식123','레니아워 RenieHouR'];
    let exception = ['인챈트 봇'];               // 매니저 봇 등 채팅배경(강조) 제외 닉네임
    const ENABLE_FIX_UNREADABLE_COLOR    = true; // 닉네임 은신 제거
    const ENABLE_REMOVE_BG_COLOR         = true; // 닉네임 형광펜 효과 제거
    const ENABLE_TRUNCATE_NICKNAME       = true; // 긴 닉네임 자르기
    const ENABLE_TOOLTIP_AUTO_CLOSE      = true; // 치트키 툴팁 자동 닫기
    // ===== 사용자 설정 변경 끝 ===========================================================================

    let chatObserver = null;
    let tooltipClosed = false; // 팝업 닫힘 여부 저장

    const LIGHT_GREEN = "rgb(102, 200, 102)";
    const Background_SKYBLUE = 'rgba(173, 216, 230, 0.15)';
    const Background_LIGHTGREEN = 'rgba(102, 200, 102, 0.15)';

    // 유틸: 닉네임 색상이 너무 어두운 경우 스타일 제거
    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 = 3) {
        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() {
        if (tooltipClosed) {
            console.log('[치트키 팝업] 이미 닫혀 있음, 감시 생략');
            return;
        }

        const observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType !== 1) continue;

                    // classList로 직접 탐색
                    const closeButton =
                          node.nodeType === 1 && node.tagName === 'BUTTON' && node.className.includes('cheat_key_tooltip_button_close__')
                              ? node
                              : node.querySelector?.('button[class*="cheat_key_tooltip_button_close__"]');

                    if (closeButton) {
                        console.log('[치트키 팝업] 닫기 버튼 클릭');
                        closeButton.click();
                        tooltipClosed = true; // 닫았음 표시
                        observer.disconnect();
                        return;
                    }
                }
            }
        });

        console.log('[치트키 팝업 감시 시작]');
        observer.observe(document.body, { childList: true, subtree: false });
    }

    // 채팅 메시지 처리 함수
    function processChatMessage(messageElem) {
        if (messageElem.getAttribute('data-partner-processed') === 'true') return;

        // 파트너 판별
        const isPartner = messageElem.querySelector('[class*="name_icon__zdbVH"]') !== null;

        // 매니저·스트리머 검사 (더 안정적인 단일 이미지 선택 방식)
        const badgeImg = messageElem.querySelector('.badge_container__a64XB img[src*="manager.png"], .badge_container__a64XB img[src*="streamer.png"]');
        const isManager = badgeImg?.src.includes('manager.png');
        const isStreamer = badgeImg?.src.includes('streamer.png');


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

        // 유틸 함수 처리: 닉네임 가독성, 형광펜 제거, 길이 자르기
        if (ENABLE_FIX_UNREADABLE_COLOR) fixUnreadableNicknameColor(nicknameElem);
        if (ENABLE_REMOVE_BG_COLOR)    removeBackgroundColor(nicknameElem);
        if (ENABLE_TRUNCATE_NICKNAME)  truncateNickname(nicknameElem, 10);

        // 지정 스트리머인지 확인
        const nameSpan = nicknameElem?.querySelector('.name_text__yQG50');
        const nameText = nameSpan ? nameSpan.textContent.trim() : '';
        const isManualStreamer = streamer.includes(nameText);

        // 연두색 적용 조건:
        // (1) 파트너이며 매니저·스트리머가 아닐 때
        // (2) 지정한 닉네임이면서 매니저·스트리머가 아닐 때
        if ((!isManager && !isStreamer) && (isPartner || isManualStreamer)) {
            if (nicknameElem) {
                nicknameElem.style.color = LIGHT_GREEN;
                nicknameElem.style.fontWeight = 'bold';
                nicknameElem.style.textTransform = 'uppercase';
            }
            if (textElem) {
                textElem.style.color = LIGHT_GREEN;
                textElem.style.fontWeight = 'bold';
                textElem.style.textTransform = 'uppercase';
            }
        }
        // 배경색 강조: 방송주인/스트리머/매니저의 채팅 배경을 강조함
        if ((isPartner || isStreamer || isManager || isManualStreamer)&& !exception.includes(nameText)) {
            messageElem.style.backgroundColor = Background_SKYBLUE;
        }
        messageElem.setAttribute('data-partner-processed', 'true');
    }

    // 채팅 MutationObserver 설정
    function setupChatObserver() {
        if (chatObserver) chatObserver.disconnect();
        const chatContainer = document.querySelector('[class*="live_chatting_list_wrapper__"], [class*="vod_chatting_list__"]');
        if (!chatContainer) {
            setTimeout(setupChatObserver, 500);
            return;
        }
        // 기존 채팅 메시지도 스캔하여 처리
        chatContainer.querySelectorAll('[class^="live_chatting_message_chatting_message__"]')
            .forEach(processChatMessage);
        // 새로운 채팅 메시지 감지
        chatObserver = new MutationObserver(mutations => {
            mutations.forEach(m => {
                m.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 });
    }

    // SPA 내 페이지 전환 감지 및 재설정
    function setupSPADetection() {
        let lastUrl = location.href;
        const onUrlChange = () => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                tooltipClosed = false; // 페이지 전환 시 다시 감시
                console.log('[SPA] URL 변경 감지, tooltipClosed 초기화');
                setTimeout(() => {
                    setupChatObserver();
                    autoClickTooltipCloseButton(); // 재등록
                }, 1000);
            }
        };
        ['pushState', 'replaceState'].forEach(method => {
            const orig = history[method];
            history[method] = function (...args) {
                orig.apply(this, args);
                onUrlChange();
            };
        });
        window.addEventListener("popstate", onUrlChange);
    }

    // 초기화 함수
    function init() {
        setupChatObserver();
        setupSPADetection();
        if (ENABLE_TOOLTIP_AUTO_CLOSE) {
        autoClickTooltipCloseButton();
        }
    }

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