DLive 화면 수정 및 렉 요소 최적화
// ==UserScript==
// @name DLive Tools
// @namespace http://tampermonkey.net/
// @version 1.4
// @description DLive 화면 수정 및 렉 요소 최적화
// @author Dacchi
// @match https://dlive.tv/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const defaultSettings = {
hideTopNav: true,
hideDonation: true,
hideTopContributors: true,
hideLike: true,
emoteScale: 50,
chatWidth: 350
};
let settings = { ...defaultSettings };
try {
const saved = localStorage.getItem('dlive-custom-settings');
if (saved) settings = { ...settings, ...JSON.parse(saved) };
} catch (e) {}
const saveSettings = () => {
localStorage.setItem('dlive-custom-settings', JSON.stringify(settings));
};
// CSS
const injectCustomCSS = () => {
if (document.getElementById('dlive-custom-style')) return;
const style = document.createElement('style');
style.id = 'dlive-custom-style';
style.textContent = `
.dplayer,.dplayer-video-wrap{width:100%!important;height:100%!important;max-height:100vh!important;flex-grow:1!important}
.dplayer-video{width:100%!important;height:100%!important;max-height:100vh!important;object-fit:contain!important}
#liveContainer{display:flex!important;height:100%!important;flex-grow:1!important}
#liveContainer>div{flex-grow:1!important;max-width:100%!important;height:100%!important}
.v-content__wrap>div>div{max-width:100%!important;height:100%!important}
body.setting-hide-nav .dlive-top-nav-target{
position:fixed!important;top:0!important;left:0!important;width:100%!important;height:56px!important;
z-index:10000!important;transition:transform .3s ease-in-out,opacity .3s ease-in-out!important;
transform:translateY(-100%)!important;opacity:0!important;background-color:rgb(15,18,20)!important}
body.setting-hide-nav #livestream-info{
position:fixed!important;top:56px!important;left:0!important;width:100%!important;z-index:9999!important;
transition:transform .3s ease-in-out,opacity .3s ease-in-out!important;
transform:translateY(calc(-100% - 56px))!important;opacity:0!important;
background-color:rgba(18,18,18,.95)!important;padding:10px!important;box-sizing:border-box!important}
body.setting-hide-nav .dlive-top-nav-target.show-element,
body.setting-hide-nav #livestream-info.show-element{transform:translateY(0)!important;opacity:1!important}
body.setting-hide-like .like-button{display:none!important}
body.setting-hide-donation .donation-wrapper{display:none!important}
body.setting-hide-contributors .top-contributors,
body.setting-hide-contributors .chatroom-header{display:none!important}
.emote-img{
max-width:calc(80px*(var(--dlive-emote-scale)/100))!important;
max-height:calc(80px*(var(--dlive-emote-scale)/100))!important;
height:auto!important;width:auto!important}
.dlive-resizer{width:8px;cursor:ew-resize;background-color:transparent;z-index:1000;position:relative;flex-shrink:0;transition:background-color .2s}
.dlive-resizer:hover,.dlive-resizer.active{background-color:rgba(255,204,0,.5)}
body.is-resizing{user-select:none!important;cursor:ew-resize!important}
body.is-resizing iframe,body.is-resizing video{pointer-events:none!important}
.dlive-settings-wrapper{position:fixed;top:28px;left:50%;transform:translate(-50%,-50%);z-index:10001;font-family:sans-serif;display:flex;flex-direction:column;align-items:center}
.dlive-settings-btn{width:36px;height:36px;background-color:#1a1c1f;color:#ffcc00;border:1px solid #333;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 5px rgba(0,0,0,.5);transition:all .2s;font-size:18px;line-height:1}
.dlive-settings-btn:hover{background-color:#2a2c2f;transform:scale(1.05)}
.dlive-settings-panel{position:absolute;top:46px;left:50%;transform:translateX(-50%);width:280px;background-color:#1a1c1f;color:#e0e0e0;border:1px solid #333;border-radius:12px;padding:16px;display:none;box-shadow:0 8px 24px rgba(0,0,0,.8)}
.dlive-settings-panel.open{display:block}
.dlive-settings-header{font-size:14px;font-weight:bold;margin-bottom:12px;color:#fff;border-bottom:1px solid #333;padding-bottom:8px}
.dlive-setting-item{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;font-size:13px}
.dlive-setting-slider-wrap{margin-bottom:15px}
.dlive-setting-slider-wrap label{display:flex;justify-content:space-between;font-size:13px;margin-bottom:8px}
.dlive-switch{position:relative;display:inline-block;width:34px;height:20px}
.dlive-switch input{opacity:0;width:0;height:0}
.dlive-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#444;transition:.4s;border-radius:20px}
.dlive-slider:before{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:white;transition:.4s;border-radius:50%}
input:checked+.dlive-slider{background-color:#ffcc00}
input:checked+.dlive-slider:before{transform:translateX(14px)}
input[type=range].dlive-range{-webkit-appearance:none;appearance:none;width:100%;height:6px;background:#555;border-radius:3px;outline:none;margin:0}
input[type=range].dlive-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:16px;width:16px;border-radius:50%;background:#ffcc00;cursor:pointer;margin-top:-5px}
input[type=range].dlive-range::-webkit-slider-runnable-track{width:100%;height:6px;cursor:pointer;background:#555;border-radius:3px}
#dlive-hover-trigger {position:fixed;top:0;left:0;width:100%;height:30px;z-index:9998;background:transparent;}
`;
document.head.appendChild(style);
};
const applySettings = () => {
const cl = document.body.classList;
cl.toggle('setting-hide-nav', !!settings.hideTopNav);
cl.toggle('setting-hide-like', !!settings.hideLike);
cl.toggle('setting-hide-donation', !!settings.hideDonation);
cl.toggle('setting-hide-contributors', !!settings.hideTopContributors);
document.documentElement.style.setProperty('--dlive-emote-scale', settings.emoteScale);
applyChatWidth(settings.chatWidth);
};
let _chatPanel = null;
const getChatPanel = () => {
if (_chatPanel && document.contains(_chatPanel)) return _chatPanel;
const chatBody = document.querySelector('.chatbody');
if (!chatBody) return null;
let el = chatBody;
while (el?.parentElement) {
if (window.getComputedStyle(el.parentElement).display === 'flex' &&
window.getComputedStyle(el.parentElement).flexDirection === 'row') break;
el = el.parentElement;
}
_chatPanel = el ?? null;
return _chatPanel;
};
const applyChatWidth = (width) => {
const panel = getChatPanel();
if (!panel) return;
const w = width + 'px';
panel.style.width = w;
panel.style.minWidth = w;
panel.style.maxWidth = w;
panel.style.flex = 'none';
};
// 설정 UI
const injectSettingsUI = () => {
if (document.getElementById('dlive-settings-wrapper')) return true;
const topNav = document.querySelector('.dlive-top-nav-target');
if (!topNav) return false;
const wrapper = document.createElement('div');
wrapper.id = 'dlive-settings-wrapper';
wrapper.className = 'dlive-settings-wrapper';
wrapper.innerHTML = `
<div class="dlive-settings-btn" id="dlive-settings-btn">⚙️</div>
<div class="dlive-settings-panel" id="dlive-settings-panel">
<div class="dlive-settings-header">UI 설정</div>
<div class="dlive-setting-item"><span>상단바 자동 숨김</span>
<label class="dlive-switch"><input type="checkbox" id="toggle-topnav" ${settings.hideTopNav?'checked':''}><span class="dlive-slider"></span></label></div>
<div class="dlive-setting-item"><span>보물상자 숨기기</span>
<label class="dlive-switch"><input type="checkbox" id="toggle-donation" ${settings.hideDonation?'checked':''}><span class="dlive-slider"></span></label></div>
<div class="dlive-setting-item"><span>기부자 숨기기</span>
<label class="dlive-switch"><input type="checkbox" id="toggle-contributors" ${settings.hideTopContributors?'checked':''}><span class="dlive-slider"></span></label></div>
<div class="dlive-setting-item"><span>하트 숨기기</span>
<label class="dlive-switch"><input type="checkbox" id="toggle-like" ${settings.hideLike?'checked':''}><span class="dlive-slider"></span></label></div>
<div class="dlive-setting-slider-wrap">
<label><span>GIF 채팅 크기</span><span id="scale-display">${settings.emoteScale}%</span></label>
<input type="range" id="slider-emote" class="dlive-range" min="10" max="100" value="${settings.emoteScale}">
</div>
<div class="dlive-setting-slider-wrap">
<label><span>채팅창 가로 크기</span><span id="width-display">${settings.chatWidth}px</span></label>
<input type="range" id="slider-chatwidth" class="dlive-range" min="200" max="800" value="${settings.chatWidth}">
</div>
</div>`;
topNav.appendChild(wrapper);
document.getElementById('dlive-settings-btn').addEventListener('click', () => {
document.getElementById('dlive-settings-panel').classList.toggle('open');
});
const bindToggle = (id, key) => {
document.getElementById(id)?.addEventListener('change', e => {
settings[key] = e.target.checked;
applySettings();
saveSettings();
});
};
bindToggle('toggle-topnav', 'hideTopNav');
bindToggle('toggle-donation', 'hideDonation');
bindToggle('toggle-contributors', 'hideTopContributors');
bindToggle('toggle-like', 'hideLike');
const emoteSlider = document.getElementById('slider-emote');
const scaleDisplay = document.getElementById('scale-display');
const widthSlider = document.getElementById('slider-chatwidth');
const widthDisplay = document.getElementById('width-display');
emoteSlider?.addEventListener('input', e => {
scaleDisplay.textContent = e.target.value + '%';
settings.emoteScale = +e.target.value;
document.documentElement.style.setProperty('--dlive-emote-scale', settings.emoteScale);
});
emoteSlider?.addEventListener('change', saveSettings);
widthSlider?.addEventListener('input', e => {
widthDisplay.textContent = e.target.value + 'px';
settings.chatWidth = +e.target.value;
applyChatWidth(settings.chatWidth);
});
widthSlider?.addEventListener('change', saveSettings);
return true;
};
let _navTagged = false;
const tagElements = () => {
if (_navTagged) return true;
const navMenu = document.querySelector('.nav-bar-menu');
if (!navMenu) return false;
let topNav = navMenu.closest('header,nav,[class*="header"],[id*="nav"]') ?? navMenu;
topNav.classList.add('dlive-top-nav-target');
navMenu.dataset.tagged = 'true';
_navTagged = true;
return true;
};
let _hoverSetupDone = false;
const setupHoverEffect = () => {
if (_hoverSetupDone) return true;
if (!document.getElementById('dlive-hover-trigger')) {
const trigger = document.createElement('div');
trigger.id = 'dlive-hover-trigger';
document.body.appendChild(trigger);
}
let navShown = false;
let hideTimeout = null;
const toggleNav = (show) => {
if (show === navShown) return;
navShown = show;
document.querySelector('.dlive-top-nav-target')?.classList.toggle('show-element', show);
document.getElementById('livestream-info')?.classList.toggle('show-element', show);
};
document.addEventListener('mouseover', e => {
if (!settings.hideTopNav) return;
const isHoveringNav = e.target.closest('#livestream-info, .dlive-top-nav-target, #dlive-settings-wrapper, #dlive-hover-trigger');
if (isHoveringNav) {
if (hideTimeout) clearTimeout(hideTimeout);
toggleNav(true);
}
});
document.addEventListener('mouseout', e => {
if (!settings.hideTopNav) return;
const isLeavingNav = !e.relatedTarget || !e.relatedTarget.closest('#livestream-info, .dlive-top-nav-target, #dlive-settings-wrapper, #dlive-hover-trigger');
if (isLeavingNav) {
if (hideTimeout) clearTimeout(hideTimeout);
hideTimeout = setTimeout(() => {
toggleNav(false);
}, 100);
}
});
_hoverSetupDone = true;
return true;
};
const setupResizer = () => {
if (document.querySelector('.dlive-resizer')) return true;
const chatPanel = getChatPanel();
if (!chatPanel?.parentElement) return false;
const container = chatPanel.parentElement;
const resizer = document.createElement('div');
resizer.className = 'dlive-resizer';
container.insertBefore(resizer, chatPanel);
let rafId = null;
const onMouseMove = (e) => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
rafId = null;
const rect = container.getBoundingClientRect();
const w = Math.round(rect.right - e.clientX);
if (w < 200 || w > 800) return;
applyChatWidth(w);
settings.chatWidth = w;
const slider = document.getElementById('slider-chatwidth');
const display = document.getElementById('width-display');
if (slider) slider.value = w;
if (display) display.textContent = w + 'px';
});
};
const onMouseUp = () => {
document.querySelector('.dlive-resizer')?.classList.remove('active');
document.body.classList.remove('is-resizing');
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
saveSettings();
};
resizer.addEventListener('mousedown', e => {
resizer.classList.add('active');
document.body.classList.add('is-resizing');
e.preventDefault();
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
return true;
};
const runInitializer = () => {
let attempts = 0;
setupHoverEffect();
const initInterval = setInterval(() => {
attempts++;
const isTagged = tagElements();
const isSettingsInjected = injectSettingsUI();
const isResizerSetup = setupResizer();
applySettings();
if ((isTagged && isSettingsInjected && isResizerSetup) || attempts > 30) {
clearInterval(initInterval);
}
}, 500);
};
const autoConfirmAge = () => {
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const tryConfirm = () => {
const checkboxes = document.querySelectorAll('.agree-check input[type="checkbox"]');
if (checkboxes.length < 2) return false;
const [cb1, cb2] = checkboxes;
if (!cb1.checked) cb1.click();
setTimeout(() => {
if (!cb2.checked) cb2.click();
setTimeout(() => {
const agreeBtn = document.querySelector('.btn.agree');
if (agreeBtn) agreeBtn.click();
}, rand(350, 450));
}, rand(350, 450));
return true;
};
if (!tryConfirm()) {
let attempts = 0;
const checkInterval = setInterval(() => {
attempts++;
if (tryConfirm() || attempts > 15) {
clearInterval(checkInterval);
}
}, 1000);
}
};
const init = () => {
injectCustomCSS();
runInitializer();
autoConfirmAge();
};
document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', init)
: init();
})();