// ==UserScript==
// @name Humoruniv Auto Refresh & Simple blind
// @namespace http://tampermonkey.net/
// @version 2.46
// @description 자동 새로고침 (옵션 및 블라인드 탭 추가)
// @author 십갈
// @match https://web.humoruniv.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=humoruniv.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
(function () {
'use strict';
if (window.self !== window.top) {
return;
}
const settings = {
autoRefresh: GM_getValue('autoRefresh', true),
blinkEffect: GM_getValue('blinkEffect', true),
blindList: GM_getValue('blindList', {}).reduce((acc, key) => {
acc[key] = 0;
return acc;
}, {})
};
function saveSettings() {
GM_setValue('autoRefresh', settings.autoRefresh);
GM_setValue('blinkEffect', settings.blinkEffect);
GM_setValue('blindList', Object.keys(settings.blindList));
}
function resetBlindValues() {
Object.keys(settings.blindList).forEach(key => {
settings.blindList[key] = 0;
});
}
function updateBlindListDisplay() {
const blindList = document.getElementById('blindList');
const currentPage = parseInt(document.getElementById('currentPage').value, 10);
const itemsPerPage = 10;
const keys = Object.keys(settings.blindList);
const totalPages = Math.max(Math.ceil(keys.length / itemsPerPage), 1);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const pageItems = keys.slice(startIndex, endIndex).map(key => `<li class="mui-list-item ${settings.blindList[key] ? 'red' : ''}" data-key="${key}">${key}</li>`);
blindList.innerHTML = pageItems.join('');
document.getElementById('totalPages').textContent = `/ ${totalPages}`;
seeViewAll();
}
function seeButtonVisible(tr) {
const span = tr.querySelector('span.hu_nick_txt');
if (!span) return;
let textContent = span.textContent.trim();
if (textContent.length === 0) {
textContent = [...span.querySelectorAll('span')].find(child => child.textContent.length > 0).textContent.trim();
}
const viewButton = tr.querySelector('button.view-button');
if (viewButton) {
if (settings.blindList.hasOwnProperty(textContent)) {
viewButton.classList.remove('hidden-button');
viewButton.classList.add('revealed-button');
targetOverlay(tr, 'block')
} else {
viewButton.classList.remove('revealed-button');
viewButton.classList.add('hidden-button');
targetOverlay(tr, 'none')
}
}
}
const settingsPanelHTML = `
<div id="settingsPanel" class="mui-panel">
<div class="mui-tabs">
<button id="settingsTab" class="mui-tab mui-active">옵션</button>
<button id="blindTab" class="mui-tab">블라인드</button>
</div>
<div id="optionsContent" class="mui-tab-content">
<label class="mui-checkbox">
<input type="checkbox" id="autoRefresh" ${settings.autoRefresh ? 'checked' : ''}>
자동 새로고침
</label><br>
<label class="mui-checkbox">
<input type="checkbox" id="blinkEffect" ${settings.blinkEffect ? 'checked' : ''}>
반짝임 효과
</label><br>
<button id="saveSettings" class="mui-btn mui-btn--primary mui-btn--raised">저장</button>
</div>
<div id="blindContent" class="mui-tab-content" style="display: none;">
<div class="mui-pagination">
<button id="prevPage" class="mui-btn mui-btn--primary mui-pagination-button"><font size=1>이전</font></button>
<input type="text" id="currentPage" value="1" class="mui-input mui-pagination-input">
<span id="totalPages">/ 1</span>
<button id="nextPage" class="mui-btn mui-btn--primary mui-pagination-button"><font size=1>다음</font></button>
<button id="deleteBlind" class="mui-btn mui-btn-primary"><font size=2>삭제</font></button>
</div>
<ul id="blindList" class="mui-list"></ul>
</div>
</div>
<div id="settingsButton" class="mui-fab" text-align: center><font size=6>⋮</font></div>
`;
GM_addStyle(`
.mui-panel {
display: none;
position: fixed;
top: 50px;
right: 50px;
width: 230px;
background: #ffffff;
border: 1px solid #e0e0e0;
padding: 10px;
z-index: 9999;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.mui-tabs {
display: flex;
border-bottom: 1px solid #e0e0e0;
}
.mui-tab {
flex: 1;
padding: 10px;
text-align: center;
cursor: pointer;
background: #f5f5f5;
border: none;
outline: none;
border-bottom: 2px solid transparent;
color: #333;
border-radius: 8px 8px 0 0;
}
.mui-active {
background: white;
border-bottom: 2px solid #007bff;
}
.mui-tab-content {
padding: 10px 0;
color: #333;
}
.mui-checkbox {
display: block;
margin: 10px 0;
}
.mui-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.mui-list-item {
border: 1px solid #e0e0e0;
padding: 10px;
margin: 5px 0;
border-radius: 8px;
cursor: pointer;
background: #fafafa;
color: #333;
}
.mui-pagination {
display: flex;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.mui-fab {
position: fixed;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
background: #007bff;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.mui-btn {
margin: 0 5px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
transition: background 0.3s;
}
.mui-btn:hover {
background: #0056b3;
}
.mui-input {
margin: 0 5px;
padding: 5px;
border: 1px solid #e0e0e0;
border-radius: 4px;
outline: none;
width: 40px;
text-align: center;
color: #333;
background: #fafafa;
}
.hidden-button {
visibility: hidden;
}
.revealed-button {
visibility: visible;
}
.red {
background-color: #ff1744;
color: white;
}
.mui-button {
margin: 2px;
padding: 2px 5px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
outline: none;
display: inline-block;
font-size: 10px;
}
.mui-button + .mui-button {
margin-left: 5px;
}
.mui-pagination-button {
border-radius: 4px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
`);
document.body.insertAdjacentHTML('beforeend', settingsPanelHTML);
document.getElementById('settingsButton').addEventListener('click', () => {
const panel = document.getElementById('settingsPanel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
if (panel.style.display === 'none') {
resetBlindValues();
saveSettings();
}
});
document.getElementById('settingsTab').addEventListener('click', () => {
document.getElementById('optionsContent').style.display = 'block';
document.getElementById('blindContent').style.display = 'none';
document.getElementById('settingsTab').classList.add('mui-active');
document.getElementById('blindTab').classList.remove('mui-active');
});
document.getElementById('blindTab').addEventListener('click', () => {
document.getElementById('optionsContent').style.display = 'none';
document.getElementById('blindContent').style.display = 'block';
document.getElementById('settingsTab').classList.remove('mui-active');
document.getElementById('blindTab').classList.add('mui-active');
updateBlindListDisplay();
});
document.getElementById('saveSettings').addEventListener('click', () => {
settings.autoRefresh = document.getElementById('autoRefresh').checked;
settings.blinkEffect = document.getElementById('blinkEffect').checked;
saveSettings();
alert('설정이 저장되었습니다.');
});
document.getElementById('blindList').addEventListener('click', (event) => {
const target = event.target;
if (target.tagName === 'LI' && target.dataset.key) {
const key = target.dataset.key;
settings.blindList[key] = settings.blindList[key] === 0 ? 1 : 0;
target.classList.toggle('red', settings.blindList[key]);
saveSettings();
}
});
document.getElementById('deleteBlind').addEventListener('click', () => {
Object.keys(settings.blindList).forEach(key => {
if (settings.blindList[key]) {
delete settings.blindList[key];
}
});
updateBlindListDisplay();
saveSettings();
});
document.getElementById('prevPage').addEventListener('click', () => {
let currentPage = parseInt(document.getElementById('currentPage').value, 10);
if (currentPage > 1) {
document.getElementById('currentPage').value = currentPage - 1;
updateBlindListDisplay();
}
});
document.getElementById('nextPage').addEventListener('click', () => {
let currentPage = parseInt(document.getElementById('currentPage').value, 10);
let totalPages = Math.max(Math.ceil(Object.keys(settings.blindList).length / 10), 1);
if (currentPage < totalPages) {
document.getElementById('currentPage').value = currentPage + 1;
updateBlindListDisplay();
}
});
const remotePageUrl = location.href;
const maxChildNodes = 20;
let shouldSync = settings.autoRefresh;
let initialAdded = false;
const style = document.createElement('style');
style.textContent = `
@keyframes blink-light {
0%, 100% { background-color: transparent; }
50% { background-color: #007bff; }
}
.blink-light {
animation: blink-light 0.5s ease-in-out 10;
}
@keyframes blink-dark {
0%, 100% { background-color: transparent; }
50% { background-color: #007bff; }
}
.blink-dark {
animation: blink-dark 0.5s ease-in-out 10;
}
.hidden-button {
visibility: hidden;
}
.revealed-button {
visibility: visible;
}
`;
document.head.appendChild(style);
function getBlinkClass() {
const darkThemeMatch = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
return darkThemeMatch ? 'blink-dark' : 'blink-light';
}
function syncElements(remoteDoc) {
if (shouldSync) {
syncSection('#post_list > tbody', remoteDoc, syncNodesWithFIFO);
refreshAdditionalElements(remoteDoc);
}
}
function syncSection(selector, remoteDoc, syncFunction) {
const currentElement = document.querySelector(selector);
const remoteElement = remoteDoc.querySelector(selector);
if (currentElement && remoteElement) {
syncFunction(currentElement, remoteElement);
}
}
function getCurrentFirstChildIdNumber(currentElement) {
const firstChild = currentElement.querySelector('tr');
if (firstChild && firstChild.id) {
const match = firstChild.id.match(/li_chk_pdswait-(\d+)/);
return match ? parseInt(match[1], 10) : 0;
}
return 0;
}
function syncNodesWithFIFO(currentNode, remoteNode) {
try {
const currentFirstIdNumber = getCurrentFirstChildIdNumber(currentNode);
const remoteChildren = Array.from(remoteNode.querySelectorAll(':scope > tr'));
const newChildren = remoteChildren.filter(child => {
const match = child.id.match(/li_chk_pdswait-(\d+)/);
const remoteIdNumber = match ? parseInt(match[1], 10) : 0;
return remoteIdNumber > currentFirstIdNumber;
});
newChildren.reverse().forEach(newChild => {
const clonedChild = newChild.cloneNode(true);
addButtons(clonedChild);
addOverlay(clonedChild, 'none');
let targetSpan = clonedChild.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span span') ||
clonedChild.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span');
let userName = targetSpan ? targetSpan.textContent.trim() : null;
if (userName) {
addOverlay(clonedChild, 'none');
if (settings.blindList.hasOwnProperty(userName)) {
targetOverlay(clonedChild, 'block')
seeButtonVisible(clonedChild)
}
}
currentNode.insertBefore(clonedChild, currentNode.firstChild);
const nextSibling = newChild.nextElementSibling;
if (nextSibling && nextSibling.tagName.toLowerCase() === 'script') {
const clonedScript = nextSibling.cloneNode(true);
currentNode.insertBefore(clonedScript, currentNode.firstChild.nextSibling);
}
});
const trChildren = Array.from(currentNode.querySelectorAll(':scope > tr'));
while (trChildren.length > maxChildNodes) {
const lastChild = trChildren.pop();
if (lastChild && currentNode.contains(lastChild)) {
const nextSibling = lastChild.nextElementSibling;
if (nextSibling && nextSibling.tagName.toLowerCase() === 'script' &&
currentNode.contains(nextSibling)) {
currentNode.removeChild(nextSibling);
}
if (currentNode.contains(lastChild)) {
currentNode.removeChild(lastChild);
}
}
}
refreshRemainingNodes(currentNode, remoteNode);
} catch (error) {
console.error('Error in syncNodesWithFIFO:', error);
}
}
function refreshRemainingNodes(currentNode, remoteNode) {
try {
const currentChildren = Array.from(currentNode.querySelectorAll(':scope > tr'));
const remoteChildren = Array.from(remoteNode.querySelectorAll(':scope > tr'));
currentChildren.forEach((currentChild) => {
const remoteChild = remoteChildren.find(remote => remote.id === currentChild.id);
const textContent = currentChild.querySelector('td.li_icn').textContent.trim();
const isInBlindList = settings.blindList.hasOwnProperty(textContent);
if (remoteChild) {
const currentChildChildren = Array.from(currentChild.children);
const remoteChildChildren = Array.from(remoteChild.children);
[1, 4, 5, 6].forEach(childIndex => {
if (childIndex === 1) {
const currentChildNode = currentChildChildren[childIndex];
const remoteChildNode = remoteChildChildren[childIndex];
if (currentChildNode && remoteChildNode && currentChildNode.textContent !== remoteChildNode.textContent) {
currentChildNode.querySelector('td.li_sbj > a').innerHTML = remoteChildNode.querySelector('td.li_sbj > a').innerHTML;
if (settings.blinkEffect && !isInBlindList) {
blinkElement(currentChildNode);
}
}
} else {
const currentChildNode = currentChildChildren[childIndex];
const remoteChildNode = remoteChildChildren[childIndex];
if (currentChildNode && remoteChildNode && currentChildNode.textContent !== remoteChildNode.textContent) {
currentChildNode.innerHTML = remoteChildNode.innerHTML;
if (settings.blinkEffect && !isInBlindList) {
blinkElement(currentChildNode);
}
}
}
});
} else {
const nextSibling = currentChild.nextElementSibling;
if (nextSibling && nextSibling.tagName.toLowerCase() === 'script' &&
currentNode.contains(nextSibling)) {
currentNode.removeChild(nextSibling);
}
currentChild.remove();
}
});
} catch (error) {
console.error('Error in refreshRemainingNodes:', error);
}
}
function blinkElement(element) {
const blinkClass = getBlinkClass();
element.classList.add(blinkClass);
setTimeout(() => {
element.classList.remove(blinkClass);
}, 2000);
}
function addButtons(trElement) {
if (trElement) {
const fourthChild = trElement.children[3];
if (!fourthChild) {
return;
}
const blindButton = document.createElement('button');
const viewButton = document.createElement('button');
blindButton.textContent = '블라';
viewButton.textContent = '보기';
viewButton.classList.add('hidden-button', 'view-button');
blindButton.classList.add('mui-button');
viewButton.classList.add('mui-button');
console.log('블라,보기 버튼 설치 완료')
blindButton.addEventListener('click', () => {
var targetSpan
let commentList = trElement.childElementCount === 6
if (commentList) {
targetSpan = trElement.querySelector('td:nth-child(2) > span > span') || trElement.querySelector('td:nth-child(2) > div:nth-child(2) > span > span');
} else {
targetSpan = trElement.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span span') ||
trElement.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span');
}
let userName = targetSpan.textContent.trim();
console.log('당신의 이름은 : ' + userName)
if (userName && settings.blindList.hasOwnProperty(userName)) {
delete settings.blindList[userName];
console.log('blind delete');
if (commentList) {
document.querySelectorAll('#cmt_wrap_box > table > tbody > tr').forEach((tr) => {
let targetSpanN = tr.querySelector('div span span span') || tr.querySelector('div span span');
let userNameN = targetSpanN ? targetSpanN.textContent.trim() : null;
if (userNameN === userName) {
targetOverlay(tr, 'none');
}
});
} else {
document.querySelectorAll('#post_list > tbody > tr').forEach((tr) => {
let targetSpanN = tr.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span span') ||
tr.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span');
let userNameN = targetSpanN ? targetSpanN.textContent.trim() : null;
if (userNameN === userName) {
targetOverlay(tr, 'none');
}
})
}
} else if (userName && !settings.blindList.hasOwnProperty(userName)) {
settings.blindList[userName] = 0;
if (commentList) {
document.querySelectorAll('#cmt_wrap_box > table > tbody > tr').forEach((tr) => {
let targetSpanN = tr.querySelector('div span span span') || tr.querySelector('div span span');
let userNameN = targetSpanN ? targetSpanN.textContent.trim() : null;
if (userNameN === userName) {
targetOverlay(tr, 'block');
}
});
} else {
document.querySelectorAll('#post_list > tbody > tr').forEach((tr) => {
let targetSpanN = tr.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span span') ||
tr.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span');
let userNameN = targetSpanN ? targetSpanN.textContent.trim() : null;
if (userNameN === userName) {
targetOverlay(tr, 'block');
}
});
}
}
updateBlindListDisplay();
saveSettings();
});
console.log('블라 이벤트 리스너 설치 완료')
viewButton.addEventListener('click', () => {
const overlays = trElement.querySelectorAll('.overlay');
if (trElement.children.length === 6 && trElement.querySelector('#list_best_box')) {
var overlayArray = [...trElement.querySelectorAll('.overlay')].slice(1);
} else {
var overlayArray = [...trElement.querySelectorAll('.overlay')];
}
overlayArray.forEach(overlay => {
if (overlay.style.display === 'none') {
overlay.style.display = 'block';
} else {
overlay.style.display = 'none';
}
});
});
fourthChild.appendChild(blindButton);
fourthChild.appendChild(viewButton);
if (trElement.childElementCount === 6) {
viewButton.insertAdjacentHTML('beforebegin', '<br>')
}
console.log('보기 이벤트 리스너 설치 완료')
}
}
function addOverlay(trElement, status) {
[0, 1, 2].forEach(i => {
if (trElement.children[i] && !trElement.children[i].querySelector('.overlay')) {
const overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.style.position = 'absolute';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.backgroundColor = 'rgba(255, 255, 255, 0.97)';
overlay.style.zIndex = '1000';
trElement.children[i].style.position = 'relative';
trElement.children[i].appendChild(overlay);
overlay.style.display = status;
}
});
}
function toggleOverlay(trElement) {
const overlays = trElement.querySelectorAll('.overlay');
overlays.forEach(overlay => {
if (overlay.style.display === 'none') {
overlay.style.display = 'block';
} else {
overlay.style.display = 'none';
}
});
}
function targetOverlay(trElement, status) {
var overlays
if (trElement.children.length === 6 && trElement.querySelector('#list_best_box')) {
overlays = [...trElement.querySelectorAll('.overlay')].slice(1);
} else {
overlays = [...trElement.querySelectorAll('.overlay')];
}
overlays.forEach(overlay => {
overlay.style.display = status;
});
}
function refreshAdditionalElements(remoteDoc) {
try {
const selectors = [
'#login_box_mem > dl > dd:nth-child(3) > a',
'#login_box_mem > dl > dd:nth-child(2) > a',
'#bbs_best'
];
selectors.forEach(selector => {
const currentElement = document.querySelector(selector);
const remoteElement = remoteDoc.querySelector(selector);
if (currentElement && remoteElement) {
currentElement.innerHTML = remoteElement.innerHTML;
}
});
const iframe = document.querySelector('#list_right_idx > div:nth-child(2) > iframe');
if (iframe) {
iframe.addEventListener('load', () => {
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
const iframeRemoteDoc = remoteDoc.querySelector('#list_right_idx > div:nth-child(2) > iframe').contentDocument || remoteDoc.querySelector('#list_right_idx > div:nth-child(2) > iframe').contentWindow.document;
const iframeCurrentElement = iframeDocument.querySelector('#wrap_right_list_new2 > div.df.wrap_sub_best_new');
const iframeRemoteElement = iframeRemoteDoc.querySelector('#wrap_right_list_new2 > div.df.wrap_sub_best_new');
if (iframeCurrentElement && iframeRemoteElement) {
iframeCurrentElement.innerHTML = iframeRemoteElement.innerHTML;
}
});
}
} catch (error) {
console.error('Error in refreshAdditionalElements:', error);
}
}
function fetchAndSyncRemotePage() {
if (settings.autoRefresh && restricted()) {
fetch(remotePageUrl, {
headers: {
'Content-Type': 'text/html; charset=euc-kr'
}
})
.then(response => response.arrayBuffer())
.then(buffer => {
const decoder = new TextDecoder('euc-kr');
const html = decoder.decode(buffer);
const parser = new DOMParser();
const remoteDoc = parser.parseFromString(html, 'text/html');
syncElements(remoteDoc);
})
.catch(error => console.error('Error fetching remote page:', error));
}
}
function observeIframe() {
const observer = new MutationObserver(() => {
const balloonIframe = document.querySelector('iframe[name="balloon"]');
shouldSync = settings.autoRefresh && !balloonIframe;
});
observer.observe(document.body, { childList: true, subtree: true });
}
function restricted() {
const url = location.href;
if (url.includes('&st=')){
return false;
} else {
return true;
}
}
function seeViewAll() {
document.querySelectorAll('#post_list > tbody > tr').forEach(seeButtonVisible);
console.log('대자에 보기버튼 추가');
document.querySelectorAll('#cmt_wrap_box > table > tbody > tr').forEach(seeButtonVisible);
console.log('답글에 보기버튼 추가');
}
if (!initialAdded) {
seeViewAll();
initialAdded = true;
console.log('초기화 성공');
}
// 댓글 창인 경우
document.querySelectorAll('#cmt_wrap_box > table > tbody > tr').forEach((trElement) => {
addButtons(trElement)
let targetSpan = trElement.querySelector('div span span span') || trElement.querySelector('div span span');
let userName = targetSpan ? targetSpan.textContent.trim() : null;
if (userName) {
addOverlay(trElement, 'none');
if (settings.blindList.hasOwnProperty(userName)) {
targetOverlay(trElement, 'block')
seeButtonVisible(trElement)
}
}
})
// 글 인 경우
document.querySelectorAll('#post_list > tbody > tr').forEach((trElement) => {
addButtons(trElement)
let targetSpan = trElement.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span span') ||
trElement.querySelector('td:nth-child(3) table tbody tr td:nth-child(2) span span');
let userName = targetSpan ? targetSpan.textContent.trim() : null;
if (userName) {
addOverlay(trElement, 'none');
if (settings.blindList.hasOwnProperty(userName)) {
targetOverlay(trElement, 'block')
seeButtonVisible(trElement)
}
}
})
updateBlindListDisplay();
window.addEventListener('load', () => {
observeIframe();
setInterval(fetchAndSyncRemotePage, 5000);
});
})();