Greasy Fork 还支持 简体中文。

Chzzk_L&V: Chatting Plus

파트너·지정 스트리머 채팅 강조 / 형광펜 제거 / 스텔스 모드 해제 / 긴 닉네임 10자 제한표시 / 치트키 팝업 자동 닫기 등 채팅 편의 기능 제공

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

您需要先安裝使用者腳本管理器擴展,如 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.8.2
// @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';

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

    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;
                    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();
    }
})();