// ==UserScript== // @name Chzzk_L&V: Chatting Plus // @namespace Chzzk_Live&VOD: Chatting Plus // @version 1.9.2 // @description "파트너이며 매니저가 아닌 유저" 또는 지정한 streamer 닉네임의 메시지를 연두색으로 표시, 채팅 닉네임 꾸미기 효과 중 스텔스모드 무력화 및 형광펜 제거, 긴 닉네임 10자 초과 시 생략 처리, 타임머신 기능 안내 및 치트키 구매 팝업 클릭하여 닫기 // @author DOGJIP // @match https://chzzk.naver.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @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'; // 기본 설정 const DEFAULTS = { streamer: ['고수달','냐 미 Nyami','새 담','청 묘','침착맨','삼식123','레니아워 RenieHouR'], exception: ['인챈트 봇','픽셀 봇','스텔라이브 봇'], fixUnreadable: true, removeHighlight: true, truncateName: true, autoCloseTooltip: true }; // 사용자 설정 불러오기(GM_getValue) let streamer = GM_getValue('streamer', DEFAULTS.streamer); let exception = GM_getValue('exception', DEFAULTS.exception); const ENABLE_FIX_UNREADABLE_COLOR = GM_getValue('fixUnreadable', DEFAULTS.fixUnreadable); const ENABLE_REMOVE_BG_COLOR = GM_getValue('removeHighlight', DEFAULTS.removeHighlight); const ENABLE_TRUNCATE_NICKNAME = GM_getValue('truncateName', DEFAULTS.truncateName); const ENABLE_TOOLTIP_AUTO_CLOSE = GM_getValue('autoCloseTooltip', DEFAULTS.autoCloseTooltip); let chatObserver = null; let tooltipClosed = false; const LIGHT_GREEN = "rgb(102, 200, 102)"; const Background_SKYBLUE = 'rgba(173, 216, 230, 0.15)'; GM_addStyle(` /* 오버레이 */ #cp-settings-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.3); display: flex; align-items: center; justify-content: center; z-index: 9999; overflow: auto; pointer-events: none; } /* 패널: 연회색 배경 */ #cp-settings-panel { background: #b0b0b0; color: #111; padding: 1rem; border-radius: 8px; width: 600px; max-width: 90%; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-family: sans-serif; pointer-events: auto; } #cp-settings-panel h3 { margin-top: 0; color: #111; } /* 입력창 */ #cp-settings-panel textarea { width: 100%; height: 80px; margin-bottom: 0.75rem; background: #fff; color: #111; border: 1px solid #ccc; border-radius: 4px; padding: 0.5rem; resize: vertical; } /* 버튼 컨테이너: flex layout */ #cp-settings-panel > div { display: flex; gap: 0.5rem; justify-content: flex-end; } /* 버튼 공통 */ #cp-settings-panel button { padding: 0.5rem 1rem; border: none; border-radius: 4px; font-size: 0.9rem; cursor: pointer; } /* 저장 버튼 */ #cp-settings-panel button#cp-save-btn, #cp-settings-panel button#cp-exc-save-btn { background: #007bff; color: #fff; } /* 취소 버튼 */ #cp-settings-panel button#cp-cancel-btn, #cp-settings-panel button#cp-exc-cancel-btn { background: #ddd; color: #111; margin-left: auto; } /* 버튼 호버 시 약간 어두워지기 */ #cp-settings-panel button:hover { opacity: 0.9; } `); function showCombinedPanel() { if (document.getElementById('cp-settings-overlay')) return; // overlay & panel 기본 구조 재사용 const overlay = document.createElement('div'); overlay.id = 'cp-settings-overlay'; const panel = document.createElement('div'); panel.id = 'cp-settings-panel'; // 현재 저장된 값 불러오기 const curStreamers = GM_getValue('streamer', DEFAULTS.streamer).join(', '); const curExceptions= GM_getValue('exception', DEFAULTS.exception).join(', '); panel.innerHTML = `

닉네임 설정




Enter ↵: 저장 Esc : 취소 (저장시 새로고침 및 적용)
`; overlay.appendChild(panel); document.body.appendChild(overlay); panel.setAttribute('tabindex', '0'); panel.focus(); panel.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); panel.querySelector('#cp-save-btn').click(); } else if (e.key === 'Escape') { e.preventDefault(); panel.querySelector('#cp-cancel-btn').click(); } }); panel.querySelector('#cp-save-btn').addEventListener('click', () => { const s = panel.querySelector('#cp-streamer-input').value; const e = panel.querySelector('#cp-exception-input').value; const fixUnread = panel.querySelector('#cp-fix-unread').checked; const removeHl = panel.querySelector('#cp-remove-hl').checked; const truncateName = panel.querySelector('#cp-truncate').checked; const autoCloseTips = panel.querySelector('#cp-auto-close').checked; GM_setValue('streamer', Array.from(new Set(s.split(',').map(x=>x.trim()).filter(x=>x))) ); GM_setValue('exception', Array.from(new Set(e.split(',').map(x=>x.trim()).filter(x=>x))) ); GM_setValue('fixUnreadable', fixUnread); GM_setValue('removeHighlight', removeHl); GM_setValue('truncateName', truncateName); GM_setValue('autoCloseTooltip', autoCloseTips); document.body.removeChild(overlay); location.reload(); }); panel.querySelector('#cp-cancel-btn').addEventListener('click', () => { document.body.removeChild(overlay); }); } // 유틸: 닉네임 색상이 너무 어두운 경우 스타일 제거 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() { if (tooltipClosed) return; const observer = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== 1) continue; const closeBtn = node.tagName === 'BUTTON' && node.className.includes('cheat_key_tooltip_button_close__') ? node : node.querySelector('button[class*="cheat_key_tooltip_button_close__"]'); if (closeBtn) { closeBtn.click(); tooltipClosed = true; observer.disconnect(); return; } } } }); 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"]'); 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); const nameText = nicknameElem?.querySelector('.name_text__yQG50')?.textContent.trim() || ''; const isManualStreamer = streamer.includes(nameText); // 연두색 스타일 if ((!isManager && !isStreamer) && (isPartner || isManualStreamer)) { nicknameElem && Object.assign(nicknameElem.style, { color: LIGHT_GREEN, fontWeight: 'bold', textTransform: 'uppercase' }); textElem && Object.assign(textElem.style, { color: LIGHT_GREEN, fontWeight: 'bold', textTransform: 'uppercase' }); } // 배경 강조 if ((isPartner || isStreamer || isManager || isManualStreamer) && !exception.includes(nameText)) { messageElem.style.backgroundColor = Background_SKYBLUE; } messageElem.setAttribute('data-partner-processed', 'true'); } // 채팅 옵저버 설정 function setupChatObserver() { if (chatObserver) chatObserver.disconnect(); const chatContainer = document.querySelector('[class*="live_chatting_list_wrapper__"], [class*="vod_chatting_list__"]'); if (!chatContainer) return setTimeout(setupChatObserver, 500); chatContainer.querySelectorAll('[class^="live_chatting_message_chatting_message__"]').forEach(processChatMessage); chatObserver = new MutationObserver(muts => muts.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; setTimeout(() => { setupChatObserver(); autoClickTooltipCloseButton(); }, 1000); } }; ['pushState','replaceState'].forEach(m => { const orig = history[m]; history[m] = function(...args) { orig.apply(this,args); onUrlChange(); }; }); window.addEventListener('popstate', onUrlChange); } // 설정 메뉴 추가 GM_registerMenuCommand("⚙️ Chzzk: Chatting Plus 설정 변경", showCombinedPanel); // 초기화 function init() { setupChatObserver(); setupSPADetection(); if (ENABLE_TOOLTIP_AUTO_CLOSE) autoClickTooltipCloseButton(); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })();