Greasy Fork is available in English.
파트너 스트리머(방송주인 포함)와 채팅관리자 채팅을 추출하여 채팅창 상단에 고정(8초)으로 표시
当前为
// ==UserScript==
// @name Chzzk_Live SpecialChatView
// @namespace Chzzk_Live SpecialChatView
// @version 1.1
// @description 파트너 스트리머(방송주인 포함)와 채팅관리자 채팅을 추출하여 채팅창 상단에 고정(8초)으로 표시
// @author 주말쾌락주의 //DOGJIP 재업로드드
// @match https://chzzk.naver.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let lastUrl = location.href;
// ===============================
// 1. 특수 채팅 고정 영역 생성 함수
// ===============================
function createSpecialChatContainer() {
const chatWrapper = document.querySelector('[class*="live_chatting_list_wrapper__"]');
if (!chatWrapper) return null;
let specialContainer = document.getElementById('special-chat-container');
if (!specialContainer) {
specialContainer = document.createElement('div');
specialContainer.id = 'special-chat-container';
specialContainer.style.position = 'absolute';
specialContainer.style.top = '50';
specialContainer.style.left = '0';
specialContainer.style.width = '100%';
specialContainer.style.zIndex = '9999';
specialContainer.style.backgroundColor = 'rgba(0,0,0,0.1)';
specialContainer.style.color = '#fff';
specialContainer.style.padding = '5px';
specialContainer.style.boxSizing = 'border-box';
chatWrapper.parentElement.insertBefore(specialContainer, chatWrapper);
chatWrapper.style.marginTop = specialContainer.offsetHeight + 'px';
}
return specialContainer;
}
// ====================================
// 2. 특수 채팅 메시지 추가 함수 (n초 후 제거)
// ====================================
function addSpecialChatMessage(nickname, message) {
const container = createSpecialChatContainer();
if (!container) return;
const chatElem = document.createElement('div');
chatElem.className = 'special-chat-message';
chatElem.style.borderBottom = '1px solid #fff';
chatElem.style.padding = '10px 5px';
chatElem.textContent = `${nickname}: ${message}`;
container.appendChild(chatElem);
updateContainerHeight();
setTimeout(() => {
if (container.contains(chatElem)) {
container.removeChild(chatElem);
updateContainerHeight();
}
}, 20000); //n초 후 사라짐(1000=1초)
}
function updateContainerHeight() {
const container = document.getElementById('special-chat-container');
if (!container) return;
const messages = container.querySelectorAll('.special-chat-message');
let totalHeight = 0;
messages.forEach(msg => {
totalHeight += msg.offsetHeight;
});
if (messages.length > 0) {
container.style.height = `${totalHeight}px`;
container.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
} else {
container.style.height = '0px';
container.style.backgroundColor = 'transparent';
}
}
// ============================================
// 3. 특정 클래스의 하위요소 존재 여부 체크 함수
// ============================================
function hasDescendantWithClass(element, classSubstring) {
return element.querySelector(`[class*="${classSubstring}"]`) !== null;
}
// ===================================
// 4. 새 채팅 메시지 처리 함수 (스트리머 등 특정 유저 확인)
// ===================================
function processChatMessage(node) {
if (node.nodeType !== 1) return;
if (!node.className.includes('live_chatting_list_item__')) return;
const Badge = node.querySelector('[class*="badge_container__"] img');
const isStreamer = Badge && Badge.src.includes("streamer.png");
const isPartner = hasDescendantWithClass(node, 'name_icon__zdbVH');
const isManager = Badge && Badge.src.includes("manager.png");
//const isFan = Badge && Badge.src.includes("fan_03.png"); //테스트용 후원뱃지 유저
if (!(isStreamer || isPartner || isManager)) return;
//if (!(isStreamer || isPartner || isManager || isFan)) return; //테스트용 후원 뱃지 유저 포함
const nicknameElem = node.querySelector('span[class^="name_text__"]');
const nickname = nicknameElem ? nicknameElem.innerText.trim() : 'Unknown';
const messageElem = node.querySelector('[class^="live_chatting_message_text__"]');
const message = messageElem ? messageElem.innerText.trim() : '';
if (nickname && message) {
addSpecialChatMessage(nickname, message);
}
}
// ===================================
// 5. 채팅 메시지 MutationObserver (진입시 마지막 채팅 저장 후 최신 채팅만 감시)
// ===================================
let lastProcessedChat = null; // ✅ 마지막으로 처리한 채팅을 저장할 변수
function startChatObserver() {
const chatList = document.querySelector('[class*="live_chatting_list_wrapper__"]');
if (chatList) {
chatObserver.disconnect(); // 중복 방지
// ✅ 처음 실행 시, 가장 마지막(최신) 채팅 메시지를 기억해둠
if (!lastProcessedChat) {
const chatItems = chatList.querySelectorAll('[class^="live_chatting_list_item__"]');
if (chatItems.length > 0) {
lastProcessedChat = chatItems[chatItems.length - 1]; // 가장 아래(최신) 메시지를 저장
console.log(`[Chzzk Script] 초기 마지막 채팅 저장 완료.`);
}
}
chatObserver.observe(chatList, { childList: true, subtree: true });
chatList.addEventListener('scroll', function() {
if (chatList.scrollTop + chatList.clientHeight < chatList.scrollHeight) {
const container = document.getElementById('special-chat-container');
if (container) {
container.innerHTML = "";
}
}
});
} else {
setTimeout(startChatObserver, 1000);
}
}
// ✅ MutationObserver 수정 (마지막 채팅 이후 새로 추가된 것만 감지)
const chatObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1 || !node.className.includes('live_chatting_list_item__')) return;
// ✅ 마지막으로 저장된 채팅 이후에 등장한 메시지만 처리
if (!lastProcessedChat || node.compareDocumentPosition(lastProcessedChat) & Node.DOCUMENT_POSITION_FOLLOWING) {
processChatMessage(node);
lastProcessedChat = node; // ✅ 최신 채팅으로 업데이트
}
});
});
});
function observeSpecialChatContainer() {
const container = document.getElementById('special-chat-container');
if (!container) return;
const observer = new MutationObserver(() => {
updateContainerHeight();
});
observer.observe(container, { childList: true, subtree: true });
}
// ===================================
// 6. 안전한 초기화 루프 (채팅창을 찾지 못하는 등 DOM생성 이전일 경우 재시도)
// ===================================
function safeInit(retryCount = 0) {
const chatList = document.querySelector('[class*="live_chatting_list_wrapper__"]');
if (chatList) {
console.log('[Chzzk Script] 채팅 리스트 탐색 성공. 초기화 시작.');
startChatObserver();
observeSpecialChatContainer();
} else {
if (retryCount < 20) {
console.log('[Chzzk Script] 채팅 리스트 미탐색. 재시도 중...', retryCount);
setTimeout(() => safeInit(retryCount + 1), 500);
} else {
console.warn('[Chzzk Script] 채팅 리스트 탐색 실패. 초기화 중단.');
}
}
}
// ===================================
// 7. SPA 대응: history.pushState / replaceState / popstate 감지 (타 방송인 화면으로 이동해도 정상적인 코드 실행)
// ===================================
(function() {
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const onUrlChange = () => {
if (location.href !== lastUrl) {
console.log('[Chzzk Script] URL 변경 감지 (SPA)', location.href);
lastUrl = location.href;
setTimeout(() => safeInit(), 500);
}
};
history.pushState = function (...args) {
originalPushState.apply(this, args);
onUrlChange();
};
history.replaceState = function (...args) {
originalReplaceState.apply(this, args);
onUrlChange();
};
window.addEventListener('popstate', onUrlChange);
})();
// ✅ 최초 진입 시 실행
safeInit();
})();