// ==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 // @downloadURL none // ==/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(); } })();