// ==UserScript==
// @name Chzzk_L&V: Chatting Plus
// @namespace Chzzk_Live&VOD: Chatting Plus
// @version 1.7
// @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'];
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) 파트너이며 매니저·스트리머가 아닐 때
// 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';
}
}
// 배경색 강조: 방송주인/스트리머/매니저의 채팅 배경을 강조함
if (isPartner || isStreamer || isManager || isManualStreamer) {
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();
}
})();