DLive Tools

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като 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();
})();