DLive Tools

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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