Warning: fopen(/www/sites/update.greasyfork.icu/index/store/temp/9737e7de1757e2c60dacba5d9adf173c.js): failed to open stream: No space left on device in /www/sites/update.greasyfork.icu/index/scriptControl.php on line 65
// ==UserScript==
// @name Chzzk AutoBlock (프로필 페이지 이동 없이 치지직 유저 자동 차단 & 기록)
// @namespace https://chzzk.naver.com/
// @version 1.4.3
// @description 치지직 채널 커뮤니티 탭에서 유저 닉네임 클릭 시 페이지 이동 없이 자동 차단, 기록 저장 및 차단 목록 iframe + 토스트 알림 제공
// @match https://chzzk.naver.com/*
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/537921/Chzzk%20AutoBlock%20%28%ED%94%84%EB%A1%9C%ED%95%84%20%ED%8E%98%EC%9D%B4%EC%A7%80%20%EC%9D%B4%EB%8F%99%20%EC%97%86%EC%9D%B4%20%EC%B9%98%EC%A7%80%EC%A7%81%20%EC%9C%A0%EC%A0%80%20%EC%9E%90%EB%8F%99%20%EC%B0%A8%EB%8B%A8%20%20%EA%B8%B0%EB%A1%9D%29.user.js
// @updateURL https://update.greasyfork.icu/scripts/537921/Chzzk%20AutoBlock%20%28%ED%94%84%EB%A1%9C%ED%95%84%20%ED%8E%98%EC%9D%B4%EC%A7%80%20%EC%9D%B4%EB%8F%99%20%EC%97%86%EC%9D%B4%20%EC%B9%98%EC%A7%80%EC%A7%81%20%EC%9C%A0%EC%A0%80%20%EC%9E%90%EB%8F%99%20%EC%B0%A8%EB%8B%A8%20%20%EA%B8%B0%EB%A1%9D%29.meta.js
// ==/UserScript==
(function () {
'use strict';
let currentChannelName = null;
let channelDetected = false;
const channelObserver = new MutationObserver(() => {
if (channelDetected) return;
const el = document.querySelector('span[class^="name_text__"]');
if (el && el.textContent.trim()) {
currentChannelName = el.textContent.trim();
channelDetected = true;
console.log('[✅ 채널명 감지됨]:', currentChannelName);
}
});
channelObserver.observe(document.body, { childList: true, subtree: true });
document.body.addEventListener('click', (e) => {
const span = e.target.closest('span[class^="name_text__"]');
if (!span) return;
const link = span.closest('a[href^="/"]');
if (!link) return;
e.preventDefault();
e.stopPropagation();
const userIdMatch = link.getAttribute('href').match(/^\/([a-z0-9]{32})$/);
if (!userIdMatch) {
console.warn('[❌ 유효한 유저 ID 아님]');
return;
}
const userId = userIdMatch[1];
handleUserId(userId);
});
async function handleUserId(userId) {
console.log('[🆔 유저 ID 감지]:', userId);
const blockedList = GM_getValue('blockedUsers', []);
const alreadyBlocked = blockedList.some(entry => entry.userId === userId);
if (alreadyBlocked) {
console.log('[⚠️ 이미 차단된 유저]:', userId);
return;
}
try {
const res = await fetch(`https://comm-api.game.naver.com/nng_main/v1/privateUserBlocks/${userId}?loungeId=`, {
method: 'POST',
credentials: 'include',
headers: {
'accept': 'application/json, text/plain, */*',
'origin': 'https://game.naver.com',
'referer': `https://game.naver.com/profile/${userId}`,
},
});
if (res.ok) {
const time = new Date().toISOString();
const entry = { userId, channelName: currentChannelName || 'unknown', time };
blockedList.push(entry);
GM_setValue('blockedUsers', blockedList);
showToast(`✅ ${userId} 차단됨`, 'success');
console.log(`[✅ 차단 성공]: ${userId} | 채널명: ${entry.channelName} | 시각: ${entry.time}`);
} else {
console.warn('[❌ 차단 실패]:', userId);
}
} catch (err) {
console.error('[❌ 차단 에러]:', err);
}
}
const originalPushState = history.pushState;
history.pushState = function (...args) {
const url = args[2];
if (typeof url === 'string' && /^\/[a-z0-9]{32}$/.test(url)) {
const userId = url.slice(1);
handleUserId(userId);
return;
}
return originalPushState.apply(this, args);
};
window.addEventListener('popstate', () => {
const path = location.pathname;
if (/^\/[a-z0-9]{32}$/.test(path)) {
const userId = path.slice(1);
handleUserId(userId);
}
});
function insertListButton() {
const target = Array.from(document.querySelectorAll('button[class^="community_detail_cell_button__"]'))
.find(btn => btn.textContent.trim() === '목록');
if (!target || document.getElementById('blockListBtn')) return;
const btn = document.createElement('button');
btn.id = 'blockListBtn';
btn.textContent = '📋 차단목록';
Object.assign(btn.style, {
marginLeft: '8px',
fontSize: '12px',
padding: '4px 8px',
backgroundColor: '#222',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
});
btn.onclick = showBlockList;
target.parentNode.insertBefore(btn, target.nextSibling);
}
function showBlockList() {
if (document.getElementById('blockListFrame')) return;
const wrapper = document.createElement('div');
wrapper.id = 'blockListFrame';
Object.assign(wrapper.style, {
position: 'fixed',
top: '10%',
left: '50%',
transform: 'translateX(-50%)',
width: '600px',
maxHeight: '80%',
backgroundColor: '#111',
color: 'white',
padding: '20px',
borderRadius: '8px',
overflowY: 'auto',
zIndex: 99999,
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
fontSize: '13px',
});
const closeBtn = document.createElement('button');
closeBtn.textContent = '닫기';
Object.assign(closeBtn.style, {
float: 'right',
backgroundColor: '#444',
color: 'white',
border: 'none',
borderRadius: '4px',
padding: '4px 8px',
cursor: 'pointer',
});
closeBtn.onclick = () => wrapper.remove();
wrapper.appendChild(closeBtn);
const filterSelect = document.createElement('select');
['전체', '5분', '1시간', '1일', '1주', '1달'].forEach(option => {
const o = document.createElement('option');
o.value = option;
o.textContent = option;
filterSelect.appendChild(o);
});
filterSelect.onchange = () => renderList(wrapper, filterSelect.value);
wrapper.appendChild(filterSelect);
const listContainer = document.createElement('div');
listContainer.id = 'blockListContent';
listContainer.style.marginTop = '20px';
wrapper.appendChild(listContainer);
document.body.appendChild(wrapper);
renderList(wrapper, '전체');
}
function renderList(wrapper, range) {
const container = wrapper.querySelector('#blockListContent');
container.innerHTML = '';
const now = Date.now();
const list = GM_getValue('blockedUsers', []).filter(entry => {
const entryTime = new Date(entry.time).getTime();
switch (range) {
case '5분': return now - entryTime <= 5 * 60 * 1000;
case '1시간': return now - entryTime <= 60 * 60 * 1000;
case '1일': return now - entryTime <= 24 * 60 * 60 * 1000;
case '1주': return now - entryTime <= 7 * 24 * 60 * 60 * 1000;
case '1달': return now - entryTime <= 30 * 24 * 60 * 60 * 1000;
default: return true;
}
});
if (list.length === 0) {
container.textContent = '차단된 유저가 없습니다.';
return;
}
list.forEach(({ userId, channelName, time }, i) => {
const div = document.createElement('div');
div.textContent = `[${i + 1}] ${channelName} | ${userId} | ${time}`;
div.style.marginBottom = '6px';
container.appendChild(div);
});
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.textContent = message;
Object.assign(toast.style, {
position: 'fixed',
top: '20px',
left: '50%',
transform: 'translateX(-50%)',
backgroundColor: type === 'success' ? '#4caf50' : '#333',
color: '#fff',
padding: '8px 16px',
borderRadius: '4px',
zIndex: 99999,
fontSize: '13px',
fontWeight: 'bold',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
opacity: 0,
transition: 'opacity 0.4s ease',
});
document.body.appendChild(toast);
requestAnimationFrame(() => {
toast.style.opacity = 1;
});
setTimeout(() => {
toast.style.opacity = 0;
setTimeout(() => toast.remove(), 400);
}, 2000);
}
const buttonObserver = new MutationObserver(insertListButton);
buttonObserver.observe(document.body, { childList: true, subtree: true });
console.log('[치지직 자동 차단 + 목록 + 토스트] 스크립트 실행됨');
})();