Chzzk_Live: Partner_Not_Manager_Green

파트너 스트리머이며 매니저가 아닌 채팅의 닉네임과 메시지를 연두색으로 표시, 은신한 닉네임 가독성 개선

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Chzzk_Live: Partner_Not_Manager_Green
// @namespace    Chzzk_Live: Partner_Not_Manager_Green Black_Nickname_Delete
// @version      1.2
// @description  파트너 스트리머이며 매니저가 아닌 채팅의 닉네임과 메시지를 연두색으로 표시, 은신한 닉네임 가독성 개선
// @author       DOGJIP
// @match        https://chzzk.naver.com/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const LIGHT_GREEN = "rgb(102,200,102)";
    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;

        // 너무 어둡거나 투명한 경우
        if (brightness < 50 || a === 0) {
            nicknameElem.style.color = ''; // 기본색으로 복원
        }
    }

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

        // 조건: 파트너이면서 매니저가 아니며, 스트리머 본인도 아님
        if (isPartner && !isManager && !isStreamer) {
            if (nicknameElem) nicknameElem.style.color = LIGHT_GREEN;
            if (textElem) textElem.style.color = LIGHT_GREEN;
        }

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

    function setupChatObserver() {
        if (chatObserver) chatObserver.disconnect();

        const chatContainer = document.querySelector('[class*="live_chatting_list_wrapper__"]');
        if (!chatContainer) {
            setTimeout(setupChatObserver, 500);
            return;
        }

        const existingMessages = chatContainer.querySelectorAll('[class^="live_chatting_message_chatting_message__"]');
        existingMessages.forEach(msg => processChatMessage(msg));

        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);
            }
        };
        const wrapHistoryMethod = (method) => {
            const original = history[method];
            history[method] = function (...args) {
                original.apply(this, args);
                onUrlChange();
            };
        };
        wrapHistoryMethod("pushState");
        wrapHistoryMethod("replaceState");
        window.addEventListener("popstate", onUrlChange);
    }

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

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