DLive Tools

DLive 화면 수정 및 렉 요소 최적화

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();
})();