Chzzk_L&V: Chatting Plus

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

当前为 2025-04-17 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴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.6
// @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';

    const LIGHT_GREEN = "rgb(102,200,102)";

    // ===== 사용자 지정 스트리머 리스트 (파트너가 아닌 스트리머의 이름을 아래의 예시처럼 정확히 넣기) =====
    let streamer = ['고수달', '백곰파', '하나노미냐', '뇨롱이', '새 담', '청 묘', '뱅배준식', '달콤레나 씨', '침착맨', '독케익'];

    let chatObserver = null;

    // 유틸: 닉네임 색상이 너무 어두운 경우 스타일 제거
    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 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');

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

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

        // 연두색 적용 조건:
        // (1) 파트너이며 매니저·스트리머가 아닐 때
        // OR
        // (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';
            }
        }

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

    // 치트키 팝업 감지 및 자동 닫기
    function setupTooltipObserver() {
        const tooltipObserver = new MutationObserver(autoClickTooltipCloseButton);
        const tooltipContainer = document.querySelector('.tooltip');
        if (tooltipContainer) {
            tooltipObserver.observe(tooltipContainer, { childList: true, subtree: 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: false });
    }

    // SPA 내 페이지 전환 감지 및 재설정
    function setupSPADetection() {
        let lastUrl = location.href;
        const onUrlChange = () => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                setTimeout(setupChatObserver, 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();
        setupTooltipObserver();
    }

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